After over a year since discovering Rust I was looking at some code I wrote a year ago, which has been in in production successfully all that time, with a view to extending it. It occurred to me that maybe use of traits and/or generics would help or at least make it look more Rustic. Shamefully I had never looked very hard at that end of the book. So now was the time to do so...
Following along with the examples I find there are three different syntaxes for passing traits as parameters. Referred to as: "trait bound syntax", "impl trait syntax" and "where syntax". Yikes. After fiddling around I sort of get the hang of it and come up with these simple examples which use the classic Circle
and Rectangle
structs and the Shape
trait which hopefully allows functions to accept either of them:
// Using "trait bound" syntax.
pub fn area_of_two_shapes<T: Shape, U: Shape> (a: &T, b: &U) -> f32 {
a.area() + b.area()
}
// Using the "impl trait" syntax.
pub fn area_of_two_shapes_impl(a: &impl Shape, b: &impl Shape) -> f32 {
a.area() + b.area()
}
// Using the "where" syntax.
pub fn area_of_two_shapes_where<T, U>(a: &T, b: &U) -> f32
where
T: Shape,
U: Shape,
{
a.area() + b.area()
}
So far so good.
Is it really so that those there syntaxes are the same? Or are there things some can do others cannot? Which of them are the currently preferred ways to proceed?
The problem comes when I start fiddling with those three syntaxes on a simpler function that accepts traits with an associated type (I believe it is called):
// Using "trait bound" syntax.
pub fn add<T: Add<Output = T>>(a: T, b: T) -> T {
a + b
}
// Using the "where" syntax.
pub fn add_where<T>(a: T, b: T) -> T
where
T: Add<Output = T>,
{
a + b
}
Try as I might I cannot find a way to do that with the impl trait
syntax. One of my best guesses being the following:
// Using the "impl trait" syntax.
pub fn add_impl<T>(a: impl Add<Output = T>, b: impl Add<Output = T>) -> T
{
a + b
}
Which fails with the rather odd error message:
error[E0308]: mismatched types
...
43 | a + b
| ^ expected type parameter `impl Add<Output = T>`, found a different type parameter `impl Add<Output = T>`
Odd because the complaint is about mismatched types but the expected and found types are the same!
So, how would it be done with the impl trait
syntax? Is it even possible to do?
Slightly at a tangent. The language used to describe these language features has been confusing. For example a trait "bound" seem to do the opposite, it adds to what a function can do rather than bounding it. For example:
pub fn do_nothing<T>(a: T) -> T {
a
}
Can do nothing with a T, it does not know what a T is, it can only return it. One has to add "bounds" to allow it to do more. Seems a bit backward.