Marker traits are used to mark types as having some property. They usually don't have any associated types and methods and are used to tell the compiler about some property of a type. Note that some of those traits are auto traits, which means that the compiler will automatically implement them for types, if it reasons it is safe to do so (you can still implement them manually, but it may require unsafe
keyword).
Currently (rustc 1.70.0
) there are 5 stable traits in std::marker
module. They have the following meaning. If type T
implements:
Copy
it means that it can be Clone
d by using bitwise copy
Send
it means that value of type T
can be send across thread boundary
Sync
it means that value of type T
can be shared between threads (that is &T
is Send
)
Sized
means that size of type T
is known at compile time
Unpin
means that value of type T
can be moved after it was pinned
Of those above Send
, Sync
and Unpin
are auto traits. There are also two more stable auto traits in std::panic module.
To answer your questions.
Why do we define a special group of traits and calling this group "marker traits"? What is special about these traits?
We call these traits "markers", to distinguish between implementation of interfaces and providing information about type properties to the compiler, both of which are accomplished in rust with traits. However there is nothing special about them. These are normal traits as all others. This is just a semantic name for us, humans.
Did I understand correctly that all these traits have default implementation and we can derive these traits for our custom structs without implementation?
They don't have default implementation strictly speaking, because by this term we understand default implementation of trait's method that is shared with all types that implement given trait and can be by them overwritten. Like for example std::io::Write::write_all.
However since they don't have any methods we can simply implement them for our types by just saying that this type implements this trait. For example we can implement Copy
for our type like this (we will used #[derive(Clone)]
so that we don't have to implement it manually, since Clone
is a supertrait of Copy
):
#[derive(Clone)]
struct Foo {
a: i32,
b: f32,
c: bool,
}
impl Copy for Foo {}
You can also implement auto traits, that compiler didn't implement automatically. For example any type that contains a raw pointer is automatically marked as !Send (negative implementations are unstable feature that compiler uses). However if it is safe to send such type between threads you can explicitly implement Send
for it (note that you must use unsafe
keyword, since wrong implementations of Send
can result in undefined behavior:
struct Bar {
ptr: *const (),
}
unsafe impl Send for Bar {}
Once exception however is trait Sized
, which can solely be implemented by the compiler. Trying to implement it manually will result in an E0322 error.
Copy
is a supertrait ofClone
" -- If I am not mistaken, it is the other way around. – Courteous