Alternative ways to write trait bounds

So far, we've been writing trait bounds as a where clause, but there are two alternative ways of writing them. The where clause is nice because it's somewhat out of the way, allowing us to write even complex trait bounds without interfering with reading the rest of the function or data type declaration.

The first alternative is to put the trait bounds alongside the generic type parameter names, like this:

impl<K: PartialOrd + PartialEq, V> TreeNode<K, V> {

For a standalone function, that technique looks like this:

fn print_generic<T: Display>(value: T) {

This can be good for data types or functions that only have simple trait bounds, but we can see that even with just two required traits, the TreeNode implementation block is getting a little hard to read. The trait bound kind of breaks up the flow and makes us go looking for the data type's name when we want to find it.

There's another way of specifying trait bounds that only works for functions:

fn requires_trait(value: impl Display)  {

What we're saying here is that the value parameter can be any data type that has the Display trait. As with any other function with generic type parameters, the compiler will generate a different version of the function for each data type that is actually used for value. However, using this syntax, we didn't give the generic type parameter a name, so we can't refer to it elsewhere in the function.

Within the body of the function, that's not usually much of a problem, because we can usually skip specifying data types inside the function body and just rely on the compiler to figure it out.

We can also use a similar syntax to specify the return type of our function, which is handy because if we don't have a name for one or more of the parameter types, it can be hard to write the return type:

fn requires_trait(value: impl Display) -> impl Display {

This doesn't mean that the function could return any data type as long as it implements Display (the correct way to do that would be to return a trait object, such as Box<dyn Display>), but all we care about is that the return type does implement Display, and we want the compiler to figure out the details of the return type beyond that.

To make that clear, here is a function that tries to return two different data types, both of which implement Display:

fn faulty_return(sel: bool) -> impl Display {
if sel {
return 52;
} else {
return "Oh no";
}
}

Here is the error message that Rust gives when we try to compile it:

When it finds the return 52, Rust checks that 52 implements Display (it does) and decides that the actual return type of the function is some form of integer. Then, it finds the second return and decides that something is wrong, because even though "Oh no" also implements Display, it's definitely not an integer. Returning an impl Display or similar doesn't mean returning anything that implements Display; it means figuring out the specific type we're returning, as long as it implements Display.

..................Content has been hidden....................

You can't read the all page of ebook, please click here login for view all page.
Reset
18.191.93.12