What is the purpose of defining empty impl in Rust?

Viewed 218

For example:

impl<T: ?Sized> cmp::Eq for $t<T> {}

Why would you even write this code if it has no implementation? It happens in many places, especially related to Eq.

#[unstable(feature = "structural_match", issue = "31434")]
impl<T: ?Sized> StructuralPartialEq for $t<T> {}

#[unstable(feature = "structural_match", issue = "31434")]
impl<T: ?Sized> StructuralEq for $t<T> {}

#[stable(feature = "rust1", since = "1.0.0")]
impl<T: ?Sized> Copy for $t<T> {}

impl<T: ?Sized> !Freeze for UnsafeCell<T> {}
unsafe impl<T: ?Sized> Freeze for PhantomData<T> {}
unsafe impl<T: ?Sized> Freeze for *const T {}
unsafe impl<T: ?Sized> Freeze for *mut T {}
unsafe impl<T: ?Sized> Freeze for &T {}
unsafe impl<T: ?Sized> Freeze for &mut T {}
2 Answers

A trait can be implemented with an empty impl {} block if it has no methods, or if all of its methods have default implementations. These are called "marker" or "tag" traits.

Abstraction without overhead: traits in Rust has a description of marker traits:

Rust has a handful of "markers" that classify types: Send, Sync, Copy, Sized. These markers are just traits with empty bodies, which can then be used in both generics and trait objects. Markers can be defined in libraries, and they automatically provide #[derive]-style implementations: if all of a types components are Send, for example, so is the type. As we saw before, these markers can be very powerful: the Send marker is how Rust guarantees thread safety.

Eq is another common marker trait. It's a tag for types that already implement PartialEq, indicating that the equality operation is not in fact "partial"; it is fully reflexive, symmetric, and transitive. Implementing it doesn't require any additional implementation since the eq implementation is taken from the type's impl PartialEq { ... } block.

pub trait Eq: PartialEq<Self> { }

Trait for equality comparisons which are equivalence relations.

This means, that in addition to a == b and a != b being strict inverses, the equality must be (for all a, b and c):

  • reflexive: a == a;
  • symmetric: a == b implies b == a; and
  • transitive: a == b and b == c implies a == c. This property cannot be checked by the compiler, and therefore Eq implies PartialEq, and has no extra methods.
Derivable

This trait can be used with #[derive]. When derived, because Eq has no extra methods, it is only informing the compiler that this is an equivalence relation rather than a partial equivalence relation. Note that the derive strategy requires all fields are Eq, which isn’t always desired.

How can I implement Eq?

If you cannot use the derive strategy, specify that your type implements Eq, which has no methods:

enum BookFormat { Paperback, Hardback, Ebook }
struct Book {
    isbn: i32,
    format: BookFormat,
}
impl PartialEq for Book {
    fn eq(&self, other: &Self) -> bool {
        self.isbn == other.isbn
    }
}
impl Eq for Book {}

Not every trait has methods you need to define. Some have methods that all come with default implementations, others have no methods at all.

However, Rust still needs to be explicitly told that any given type implements a trait. Rust does not do "duck typing" (such as in golang) where traits are implicitly implemented if the right method signatures exist, you always have to do implement a trait explicitly. There are good design reasons for this, but perhaps that can be researched separately if you're interested.

Thus, for implementing traits either by using only default method implementations, or a trait that has no methods at all, you still need to write the impl block. Of course, that impl block will be empty in those cases, since all it's saying is "struct Foo implements trait Bar", with no further information required for the compiler.

There are lots of good reasons to write traits that don't require methods to be defined. A common one is "marker traits" which simply mark something about what the type is/how it behaves/what properties it has, but doesn't require any methods to evidence this. Another fairly common one is sealed traits which allows you to have a pub trait (so your users can see and use the methods on) where nobody else can impl it outside of your crate (so you can guarantee that any type that implements that trait is one you control).

Related