Are Rust traits analogous to JavaScript mixins? - rust

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?

"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.

Thinking of traits as mixins will lead you away from, rather than towards, understanding. Traits are fundamentally about the strict type system, which will be quite alien to a programmer whose native language is JavaScript.
Like most programming constructs, traits are flexible enough that one could use them in a way that resembles how mixins are idiomatically used, but that won't resemble at all how most other programmers, including the standard library, use traits.
You should think of traits as a radical novelty.

Traits or "type classes" (in Haskell, which is where Rust got traits from) are fundamentally about logical constraints on types. Traits are not fundamentally about values. Since JavaScript is unityped, mixins, which are about values, are nothing like traits/type-classes in a statically typed language like Rust or Haskell. Traits let us talk in a principled way about the commonalities between types. Unlike C++, which has "templates", Haskell and Rust type check implementations before monomorphization.
Assuming a generic function:
fn foo<T: Trait>(x: T) { /* .. */ }
or in Haskell:
foo :: Trait t => t -> IO ()
foo = ...
The bound T: Trait means that any type T you pick must satisfy the Trait. To satisfy the Trait, the type must explicitly say that it is implementing the Trait and therein provide a definition of all items required by the Trait. In order to be sound, Rust also guarantees that each type implements a given trait at most once - therefore, there can never be overlapping implementations.
Consider the following marker trait and a type which implements it:
trait Foo {}
struct Bar;
impl Foo for Bar {}
or in Haskell:
class Foo x where
data Bar = Bar
instance Foo Bar where
Notice that Foo does not have any methods, functions, or any other items. A difference between Haskell and Rust here is that x is absent in the Rust definition. This is because the first type parameter to a trait is implicit in Rust (and referred to by with Self) while it is explicit in Haskell.
Speaking of type parameters, we can define the trait StudentOf between two types like so:
trait StudentOf<A> {}
struct AlanTuring;
struct AlonzoChurch;
impl StudentOf<AlonzoChurch> for AlanTuring {}
or in Haskell:
class StudentOf self a where
data AlanTuring = AlanTuring
data AlonzoChurch = AlonzoChurch
instance StudentOf AlanTuring AlonzoChurch where
Until now, we've not introduced any functions - let's do that:
trait From<T> {
fn from(x: T) -> Self;
}
struct WrapF64(f64);
impl From<f64> for WrapF64 {
fn from(x: f64) -> Self {
WrapF64(x)
}
}
or in Haskell:
class From self t where
from :: t -> self
newtype WrapDouble = WrapDouble Double
instance From WrapDouble Double where
from d = WrapDouble d
What you've seen here is also a form of return type polymorphism. Let's make it a bit more clear and consider a Monoid trait:
trait Monoid {
fn mzero() -> Self;
fn mappend(self, rhs: Self) -> Self;
}
struct Sum(usize);
impl Monoid for Sum {
fn mzero() -> Self { Sum(0) }
fn mappend(self, rhs: Self) -> Self { Sum(self.0 + rhs.0) }
}
fn main() {
let s: Sum = Monoid::mzero();
let s2 = s.mappend(Sum(2));
// or equivalently:
let s2 = <Sum as Monoid>::mappend(s, Sum(2));
}
or in Haskell:
class Monoid m where
mzero :: m -- Notice that we don't have any inputs here.
mappend :: m -> m -> m
...
The implementation of mzero here is inferred by the required return type Sum, which is why it is called return type polymorphism. Another subtle difference here is the self syntax in mappend - this is mostly a syntactic difference that allows us to do s.mappend(Sum(2)); in Rust.
Traits also allow us to require that each type which implements the trait must provide an associated item, such as associated constants:
trait Identifiable {
const ID: usize; // Each impl must provide a constant value.
}
impl Identifiable for bool {
const ID: usize = 42;
}
or associated types:
trait Iterator {
type Item;
fn next(&mut self) -> Option<Self::Item>;
}
struct Once<T>(Option<T>);
impl<T> Iterator for Once<T> {
type Item = T;
fn next(&mut self) -> Option<Self::Item> {
self.0.take()
}
}
Associated types also allow us to define functions on the type level rather than functions on the value level:
trait UnaryTypeFamily { type Output: Clone; }
impl UnaryTypeFamily for InputType { Output = String; }
fn main() {
// Apply the function UnaryTypeFamily with InputType.
let foo: <InputType as UnaryTypeFamily>::Output = String::new();
}
Some traits such as Iterator are also object safe. This means that you can erase the actual type behind a pointer, and a vtable will be created for you:
fn use_boxed_iter(iter: Box<Iterator<Item = u8>>) { /* .. */ }
The Haskell equivalent of trait objects are existentially quantified types, which in fact trait objects are in a type theoretical sense.
Finally, there's the issue of higher kinded types, which lets us be generic over type constructors. In Haskell, you can formulate what it means to be an (endo)functor like so:
class Functor (f :: * -> *) where
fmap :: (a -> b) -> (f a -> f b)
At this point, Rust does not have an equivalent notion, but will be equally expressive with generic associated types (GATs) soon:
trait FunctorFamily {
type Functor<T>;
fn fmap<A, B, F>(self: Self::Functor<A>, mapper: F) -> Self::Functor<B>
where F: Fn(A) -> B;
}

To add to schwern's answer
A mixin is a subclass specification that may be applied to various
parent classes in order to extend them with the same set of features. - Traits: Composable Units of Behaviour.
The major difference compared to trait is that they have "total ordering". Changing the order in which mixins are implemented for a class or strut can cause the behaviour of the class or struct to change. If mixins X, Y were applied to a struct or class A, then applying X after Y can give you a different behaviour compared to when you apply Y after X. Traits are independent of implementation order - i.e has flattened code.

Related

How can I use traits more efficiently in a solver for coupled differential equations?

some context
I am writing a solver for systems of (delay-)coupled differential equations i.e. "integrating the equations". Similar to the Lorenz-System (no delay feedback) or the Mackey-Glass-system (with delay-feedback), but with (potentially) multiple of such systems that feedback into each other either directly or after a certain delay. The feedback is through a complex network topology. I started using traits in order to restrict how a certain system can be defined so that it can work with the rest of my code (using Runge-Kutta methods for example). In general I have two kinds of systems - with and without delay (for which I have some special cases each that I'd like to optimize the integration process a little). Right now I feel I have to write everything two or more times and this is inefficient hard to understand.
Every "dynamical system" has a "state" (think of a vector of coordinates) and a function f(state) which gives the derivative for a state. Think about it like a vector-field that attaches an arrow to every point in space. Model is just a set of parameters important for f, that characterize a given system.
My two traits I thought of:
pub trait DynamicalSystem {
type StateT;
type ModelT; // the model parameters
fn f(state: &StateT, model: &ModelT) -> StateT;
}
pub trait DynamicalDelaySystem {
type StateT;
type ModelT;
type DelayT;
fn f(state: &StateT, model: &ModelT, delay: &DelayT) -> StateT;
fn keep_delay(state: &StateT) -> DelayT; // keep *something* for the feedback
}
If I am integrating this with the Euler method I need to define two integrations:
euler(state: &mut StateT, model: &ModelT, dt: &f64, f: fn(&StateT, &ModelT) -> StateT) {
*state += f(&state, &model) * dt;
}
euler_delay(state: &mut StateT, model: &ModelT, delay: &DelayT, dt: &f64, f: fn(&StateT, &ModelT) -> StateT, keep_delay: fn(&StateT) -> DelayT) {
*state += f(&state, &model, &delay) * dt;
keep_delay(state); // some function that puts the delay into some container e.g. a ringbuffer for later use
}
My idea was to write an "Integrator"-struct that keeps a vector of StateT's, ModelT's and so on. But I need to do it twice - once with and once without delay.
struct Integrator<SystemT> {
dt: f64,
states: Vec<SystemT::StateT>,
models: Vec<SystemT::ModelT>,
}
impl<SystemT> Integrator<SystemT> {
fn integration_step(&mut self) {
for (s, m) in &mut self.states.iter_mut().zip(self.models) {
euler_step(&mut s, &m, &dt);
}
struct DelayIntegrator<SystemT> {
// the same as `Integrator<SystemT>`
history: Vec<Vec<SystemT::DelayT>>,
}
impl<SystemT> DelayIntegrator<SystemT> {
// everything again, but retrieve delayed values and save new values after integration step.
}
This seems kind of redundant. Even worse: Besides the general case I have special cases that I want to address (i.e. optimize for):
Only one single dynamical system (states.len() == models.len() == 1)
multiple dynamical systems with identical parameters (states.len() > 1 && models.len() == 1)
Feedback between systems, but no delay i.e. I don't need a complicated container to store delay-values in.
no (delay-)feedback
My first try to solve 1 and 2 was to use a function pointer for integration_step that I set in the new-method of the Integrator. Depending on which case of the system is setup (number of systems, number of model parameters). But function pointers seem like a hack that would be slow.
question:
What general advice for refactorization / more "DRY" code would you recommend here? I am unsure if I understand the full potential of traits, yet. Especially combining multiple traits instead of writing everything into one is difficult for me to conceive. Could I split this out into multiple traits like DelayFeedback, InstantFeedback, NoFeedback, SingleSystem, MultipleIdenticalSystems and MultipleDistinctSystems? I seem unable to use traits to enforce the existence of a structs member-variable e.g. the delay-container.
It seems to me that a DynamicalSystem is a special case of a DynamicalDelaySystem where DelayT = (). That observation may let you eliminate the DynamicalSystem trait.
pub trait DynamicalSystem {
type StateT;
type ModelT;
type DelayT;
fn next(state: &Self::StateT, model: &Self::ModelT, delay: &Self::DelayT) -> Self::StateT;
fn keep_delay(state: &Self::StateT) -> Self::DelayT;
}
impl DynamicalSystem for usize {
type StateT = usize;
type ModelT = usize;
type DelayT = (); // there is no delay
fn next(state: &Self::StateT, model: &Self::ModelT, delay: &Self::DelayT) -> Self::StateT {
*state
}
fn keep_delay(state: &Self::StateT) -> Self::DelayT {
()
}
}
Or maybe try the experimental trait alias feature:
#![feature(trait_alias)]
pub trait DynamicalSystem = DynamicalDelaySystem<DelayT = ()>;
pub trait DynamicalDelaySystem {
type StateT;
type ModelT;
type DelayT;
fn next(state: &Self::StateT, model: &Self::ModelT, delay: &Self::DelayT) -> Self::StateT;
fn keep_delay(state: &Self::StateT) -> Self::DelayT;
}
Another possible way to do it is to make DynamicalSystem a super trait of DynamicalDelaySystem:
pub trait DynamicalSystem {
type StateT;
type ModelT;
fn next(state: &Self::StateT, model: &Self::ModelT) -> Self::StateT;
}
pub trait DynamicalDelaySystem: DynamicalSystem {
type DelayT;
fn next_with_delay(
state: &Self::StateT,
model: &Self::ModelT,
delay: &Self::DelayT,
) -> Self::StateT;
fn keep_delay(state: &Self::StateT) -> Self::DelayT;
}
Finally, if nothing else is good enough, you could use macros to do the code duplication work for you.

Associated type for ndarray arguments in rust

I want to create an interface for a (numeric) algorithm for which I want to provide an implementation with ndarray and similar libraries (let's say pytorch bindings)
struct A<D> {
array: D
}
trait<D> T {
type ArgType;
fn foo(&mut self, other: &ArgType);
}
The type of the second argument other should depend on the selected generic type D. With ndarray, there are two types of arrays–those owning their data and views that share their data. The best way is to accept both types seems to be something like the following:
fn bar<S>(a: &ArrayBase<S, Ix2>)
where
S: Data<Elem = f64> {}
For the implementation of the trait T for A using ndarray, that would mean that I need something like this
use ndarray::{prelude::*, Data};
impl T<Array2<f64>> for A<Array2<f64>> {
type ArgType<S>=ArrayBase<S: Data<Elem=f64>, Ix2>;
fn foo(&mut self, other: &Self::ArgType){
///
}
}
Then, however, how would I add the new template parameter to foo. More importantly, generic associated types are not allowed in stable. I assume it would be simpler if there were a trait in ndarray that defined all methods, however, they are implemented directly for the ArrayBase type. I thought about writing a thin abstraction layer but that would be too complex (even if I only use a small subset of methods only). The asarray trait seemed to be a promising solution but it requires an lifetime parameter too (is there even a concept of associated traits?).
What would be the recommended way of handling this kind of situation; is there an easy way?
Maybe this approach is what you want:
use ndarray::{ArrayBase, Ix2};
trait TheAlgorithm<OtherArg> {
fn calculate(&self, other: &OtherArg) -> f64;
}
impl<ReprSelf, ReprOther> TheAlgorithm<ArrayBase<ReprOther, Ix2>>
for ArrayBase<ReprSelf, Ix2>
where
ReprSelf: ndarray::Data<Elem = f64>,
ReprOther: ndarray::Data<Elem = f64>,
{
fn calculate(&self, other: &ArrayBase<ReprOther, Ix2>) -> f64 {
// dummy calculation
self.view()[(0, 0)] + other.view()[(0, 0)]
}
}
This lets you call the calculate() method on either owned arrays or views, and the other argument can be owned or a view in either case.

What is a canonical approach for having lite concrete-specific behaviour in Rust? [duplicate]

How do I get Box<B> or &B or &Box<B> from the a variable in this code:
trait A {}
struct B;
impl A for B {}
fn main() {
let mut a: Box<dyn A> = Box::new(B);
let b = a as Box<B>;
}
This code returns an error:
error[E0605]: non-primitive cast: `std::boxed::Box<dyn A>` as `std::boxed::Box<B>`
--> src/main.rs:8:13
|
8 | let b = a as Box<B>;
| ^^^^^^^^^^^
|
= note: an `as` expression can only be used to convert between primitive types. Consider using the `From` trait
There are two ways to do downcasting in Rust. The first is to use Any. Note that this only allows you to downcast to the exact, original concrete type. Like so:
use std::any::Any;
trait A {
fn as_any(&self) -> &dyn Any;
}
struct B;
impl A for B {
fn as_any(&self) -> &dyn Any {
self
}
}
fn main() {
let a: Box<dyn A> = Box::new(B);
// The indirection through `as_any` is because using `downcast_ref`
// on `Box<A>` *directly* only lets us downcast back to `&A` again.
// The method ensures we get an `Any` vtable that lets us downcast
// back to the original, concrete type.
let b: &B = match a.as_any().downcast_ref::<B>() {
Some(b) => b,
None => panic!("&a isn't a B!"),
};
}
The other way is to implement a method for each "target" on the base trait (in this case, A), and implement the casts for each desired target type.
Wait, why do we need as_any?
Even if you add Any as a requirement for A, it's still not going to work correctly. The first problem is that the A in Box<dyn A> will also implement Any... meaning that when you call downcast_ref, you'll actually be calling it on the object type A. Any can only downcast to the type it was invoked on, which in this case is A, so you'll only be able to cast back down to &dyn A which you already had.
But there's an implementation of Any for the underlying type in there somewhere, right? Well, yes, but you can't get at it. Rust doesn't allow you to "cross cast" from &dyn A to &dyn Any.
That is what as_any is for; because it's something only implemented on our "concrete" types, the compiler doesn't get confused as to which one it's supposed to invoke. Calling it on an &dyn A causes it to dynamically dispatch to the concrete implementation (again, in this case, B::as_any), which returns an &dyn Any using the implementation of Any for B, which is what we want.
Note that you can side-step this whole problem by just not using A at all. Specifically, the following will also work:
fn main() {
let a: Box<dyn Any> = Box::new(B);
let _: &B = match a.downcast_ref::<B>() {
Some(b) => b,
None => panic!("&a isn't a B!")
};
}
However, this precludes you from having any other methods; all you can do here is downcast to a concrete type.
As a final note of potential interest, the mopa crate allows you to combine the functionality of Any with a trait of your own.
It should be clear that the cast can fail if there is another type C implementing A and you try to cast Box<C> into a Box<B>. I don't know your situation, but to me it looks a lot like you are bringing techniques from other languages, like Java, into Rust. I've never encountered this kind of Problem in Rust -- maybe your code design could be improved to avoid this kind of cast.
If you want, you can "cast" pretty much anything with mem::transmute. Sadly, we will have a problem if we just want to cast Box<A> to Box<B> or &A to &B because a pointer to a trait is a fat-pointer that actually consists of two pointers: One to the actual object, one to the vptr. If we're casting it to a struct type, we can just ignore the vptr. Please remember that this solution is highly unsafe and pretty hacky -- I wouldn't use it in "real" code.
let (b, vptr): (Box<B>, *const ()) = unsafe { std::mem::transmute(a) };
EDIT: Screw that, it's even more unsafe than I thought. If you want to do it correctly this way you'd have to use std::raw::TraitObject. This is still unstable though. I don't think that this is of any use to OP; don't use it!
There are better alternatives in this very similar question: How to match trait implementors

Retrieving generic struct from trait object [duplicate]

How do I get Box<B> or &B or &Box<B> from the a variable in this code:
trait A {}
struct B;
impl A for B {}
fn main() {
let mut a: Box<dyn A> = Box::new(B);
let b = a as Box<B>;
}
This code returns an error:
error[E0605]: non-primitive cast: `std::boxed::Box<dyn A>` as `std::boxed::Box<B>`
--> src/main.rs:8:13
|
8 | let b = a as Box<B>;
| ^^^^^^^^^^^
|
= note: an `as` expression can only be used to convert between primitive types. Consider using the `From` trait
There are two ways to do downcasting in Rust. The first is to use Any. Note that this only allows you to downcast to the exact, original concrete type. Like so:
use std::any::Any;
trait A {
fn as_any(&self) -> &dyn Any;
}
struct B;
impl A for B {
fn as_any(&self) -> &dyn Any {
self
}
}
fn main() {
let a: Box<dyn A> = Box::new(B);
// The indirection through `as_any` is because using `downcast_ref`
// on `Box<A>` *directly* only lets us downcast back to `&A` again.
// The method ensures we get an `Any` vtable that lets us downcast
// back to the original, concrete type.
let b: &B = match a.as_any().downcast_ref::<B>() {
Some(b) => b,
None => panic!("&a isn't a B!"),
};
}
The other way is to implement a method for each "target" on the base trait (in this case, A), and implement the casts for each desired target type.
Wait, why do we need as_any?
Even if you add Any as a requirement for A, it's still not going to work correctly. The first problem is that the A in Box<dyn A> will also implement Any... meaning that when you call downcast_ref, you'll actually be calling it on the object type A. Any can only downcast to the type it was invoked on, which in this case is A, so you'll only be able to cast back down to &dyn A which you already had.
But there's an implementation of Any for the underlying type in there somewhere, right? Well, yes, but you can't get at it. Rust doesn't allow you to "cross cast" from &dyn A to &dyn Any.
That is what as_any is for; because it's something only implemented on our "concrete" types, the compiler doesn't get confused as to which one it's supposed to invoke. Calling it on an &dyn A causes it to dynamically dispatch to the concrete implementation (again, in this case, B::as_any), which returns an &dyn Any using the implementation of Any for B, which is what we want.
Note that you can side-step this whole problem by just not using A at all. Specifically, the following will also work:
fn main() {
let a: Box<dyn Any> = Box::new(B);
let _: &B = match a.downcast_ref::<B>() {
Some(b) => b,
None => panic!("&a isn't a B!")
};
}
However, this precludes you from having any other methods; all you can do here is downcast to a concrete type.
As a final note of potential interest, the mopa crate allows you to combine the functionality of Any with a trait of your own.
It should be clear that the cast can fail if there is another type C implementing A and you try to cast Box<C> into a Box<B>. I don't know your situation, but to me it looks a lot like you are bringing techniques from other languages, like Java, into Rust. I've never encountered this kind of Problem in Rust -- maybe your code design could be improved to avoid this kind of cast.
If you want, you can "cast" pretty much anything with mem::transmute. Sadly, we will have a problem if we just want to cast Box<A> to Box<B> or &A to &B because a pointer to a trait is a fat-pointer that actually consists of two pointers: One to the actual object, one to the vptr. If we're casting it to a struct type, we can just ignore the vptr. Please remember that this solution is highly unsafe and pretty hacky -- I wouldn't use it in "real" code.
let (b, vptr): (Box<B>, *const ()) = unsafe { std::mem::transmute(a) };
EDIT: Screw that, it's even more unsafe than I thought. If you want to do it correctly this way you'd have to use std::raw::TraitObject. This is still unstable though. I don't think that this is of any use to OP; don't use it!
There are better alternatives in this very similar question: How to match trait implementors

What is the syntax and semantics of the `where` keyword?

Unfortunately, Rust's documentation regarding where is very lacking. The keyword only appears in one or two unrelated examples in the reference.
What semantic difference does where make in the following code? Is there any difference at all? Which form is preferred?
fn double_a<T>(a: T) -> T where T: std::num::Int {
a + a
}
fn double_b<T: std::num::Int>(a: T) -> T {
a + a
}
In the implementation of the CharEq trait, it seems that where is being used as some sort of "selector" to implement Trait for anything that matches some closure type. Am I correct?
Is there any way I can get a better, more complete picture of where? (full specification of usage and syntax)
In your example, the two codes are strictly equivalent.
The where clauses were introduced to allow more expressive bound-checking, doing for example :
fn foo<T>(a: T) where Bar<T>: MyTrait { /* ... */ }
Which is not possible using only the old syntax.
Using where rather than the original syntax is generally preferred for readability even if the old syntax can still be used.
You can imagine for example constructions like
fn foo<A, B, C>(a: A, b: B, c: C)
where A: SomeTrait + OtherTrait,
B: ThirdTrait<A>+ OtherTrait,
C: LastTrait<A, B>
{
/* stuff here */
}
which are much more readable this way, even if the could still be expressed using the old syntax.
For your question about the CharEq trait, the code is:
impl<F> CharEq for F where F: FnMut(char) -> bool {
#[inline]
fn matches(&mut self, c: char) -> bool { (*self)(c) }
#[inline]
fn only_ascii(&self) -> bool { false }
}
It literally means: Implementation of trait CharEq for all type F that already implements the trait FnMut(char) -> bool (that is, a closure or a function taking a char and returning a bool).
For more details, you can look at the RFC that introduced the where clauses : https://github.com/rust-lang/rfcs/pull/135

Resources