The Rust book (2nd Edition) suggests that "Traits are similar to a feature often called ‘interfaces’ in other languages, though with some differences." For those not familiar with interfaces, the analogy doesn't illuminate. Can traits be reasonably thought of as mixins such as those found commonly in JavaScript?
They both seem to be a way to share code and add methods to multiple types/objects without inheritance, but how crucial are the differences for conceptual understanding?
Unlike in JavaScript, where mixins are a neat add-on, traits are a fundamental part of Rust.
No, Rust is not Java and the concepts don't map exactly. In particular AFAIK Java interfaces don't have an equivalent for Rust's extension traits.
The definition of mixins can be stated as mixins is a class that contains methods that can be used by other classes without inheriting from that class. The methods in mixin provide certain behavior which is not used alone but can be used to add these behaviors to other classes.
A trait tells the Rust compiler about functionality a particular type has and can share with other types. Traits are an abstract definition of shared behavior amongst different types. So, we can say that traits are to Rust what interfaces are to Java or abstract classes are to C++.
"Traits" (or "Roles" in Perl) are a way to add multiple units of functionality to a class (or struct in Rust) without the problems of multiple inheritance. Traits are "cross cutting concerns" meaning they're not part of the class hierarchy, they can be potentially implemented on any class.
Traits define an interface, meaning in order for anything to implement that trait it must define all the required methods. Like you can require that method parameters be of a certain classes, you can require that certain parameters implement certain traits.
A good example is writing output. In many languages, you have to decide if you're writing to a FileHandle
object or a Socket
object. This can get frustrating because sometimes things will only write to files, but not sockets or vice versa, or maybe you want to capture the output in a string for debugging.
If you instead define a trait, you can write to anything that implements that trait. This is exactly what Rust does with std::io::Write
.
pub trait Write {
fn write(&mut self, buf: &[u8]) -> Result<usize>;
fn flush(&mut self) -> Result<()>;
fn write_all(&mut self, mut buf: &[u8]) -> Result<()> {
while !buf.is_empty() {
match self.write(buf) {
Ok(0) => return Err(Error::new(ErrorKind::WriteZero,
"failed to write whole buffer")),
Ok(n) => buf = &buf[n..],
Err(ref e) if e.kind() == ErrorKind::Interrupted => {}
Err(e) => return Err(e),
}
}
Ok(())
}
...and a few more...
}
Anything which wants to implement Write
must implement write
and flush
. A default write_all
is provided, but you can implement your own if you like.
Here's how Vec<u8>
implements Write
so you can "print" to a vector of bytes.
impl Write for Vec<u8> {
fn write(&mut self, buf: &[u8]) -> io::Result<usize> {
self.extend_from_slice(buf);
Ok(buf.len())
}
fn write_all(&mut self, buf: &[u8]) -> io::Result<()> {
self.extend_from_slice(buf);
Ok(())
}
fn flush(&mut self) -> io::Result<()> { Ok(()) }
}
Now when you write something that needs to output stuff instead of deciding if it should write to a File
or a TcpStream
(a network socket) or whatever, you say it just has to have the Write
trait.
fn display( out: Write ) {
out.write(...whatever...)
}
Mixins are a severely watered down version of this. Mixins are a collection of methods which get injected into a class. That's about it. They solve the problem of multiple inheritance and cross-cutting concerns, but little else. There's no formal promise of an interface, you just call the methods and hope for the best.
Mixins are mostly functionally equivalent, but provide none of the compile time checks and high performance that traits do.
If you're familiar with mixins, traits will be a familiar way to compose functionality. The requirement to define an interface will be the struggle, but strong typing will be a struggle for anyone coming to Rust from JavaScript.
Unlike in JavaScript, where mixins are a neat add-on, traits are a fundamental part of Rust. They allow Rust to be strongly-typed, high-performance, very safe, but also extremely flexible. Traits allow Rust to perform extensive compile time checks on the validity of function arguments without the traditional restrictions of a strongly typed language.
Many core pieces of Rust are implemented with traits. std::io::Writer
has already been mentioned. There's also std::cmp::PartialEq
which handles ==
and !=
. std::cmp::PartialOrd
for >
, >=
, <
and <=
. std::fmt::Display
for how a thing should be printed with {}
. And so on.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With