Skip to content

Instantly share code, notes, and snippets.

@Oyelowo
Created June 1, 2022 15:12
Show Gist options
  • Save Oyelowo/dfbc5872d4c2eafc66847772d241544d to your computer and use it in GitHub Desktop.
Save Oyelowo/dfbc5872d4c2eafc66847772d241544d to your computer and use it in GitHub Desktop.
Blogpost1
The nuances of Rust's `impl Trait` syntactic sugar
A brief discussion in a discord channel about language design is the motivation behind this writing.
What happens when you have gthe function below:
```rs
trait Speed { }
fn get_oyelowo(arg1: impl Speed , arg2: impl Speed) {
let a = arg1;
let b = arg2;
a = arg2; // This gives an error
}
```
Rust would give the error below, even though they implement thesame trait.
```rs
mismatched types
expected type parameter `impl Speed` (type parameter `impl Speed`)
found type parameter `impl Speed` (type parameter `impl Speed`)
a type parameter was expected, but a different one was found; you might be missing a type parameter or trait bound
```
but when you try this, it works:
```rs
fn get_oyelowo_generics<T: Speed>(arg1: T , arg2: T) {
let a = arg1;
let b = arg2;
a = arg2;
}
```
So, why is that?
#### ANSWER
Actually, when you use that syntactic sugar - `impl Trait`, Rust, essentially creates
separate generics for all the arguments because, despite the arguments implementing
same trait, they might actually have different concrete types when the function is called.
Which means function 1 is actually "desugared" to function 2 rather than function 3:
```rs
trait Speed { }
// FUNCTION 1
fn get_oyelowo_variation1(arg1: impl Speed , arg2: impl Speed) {
let a = arg1;
let b = arg2;
a = arg2;
}
// FUNCTION 2
// This is the right one - the arguments are separate - and
// can have different concrete types
fn get_oyelowo_variation2<A: Speed, B: Speed>(arg1: A , arg2: B) {
let a = arg1;
let b = arg2;
a = arg2;
}
// FUNCTION 3
// This is not the desugared version because it ties the arguments
// to thesame concrete types
fn get_oyelowo_variation3<A: Speed>(arg1: A , arg2: A) {
let a = arg1;
let b = arg2;
a = arg2;
}
```
### Example
Say, we have various structs that implement the trait e.g
```rs
trait SpaceTrait { }
struct SpaceHuman {
id: Uuid,
base: String,
}
impl SpaceTrait for SpaceHuman {
}
struct SpaceDog {
id: Uuid,
breed: DogBreed,
}
impl SpaceTrait for SpaceDog {
}
```
In this case, let's call the function on the above concrete types.
If the above were to be desugared to thesame concrete type, we would not be
able to compare the two creatures, even though they both implement thesame trait
```rs
// The function:
fn compare_attributes(creature1: impl SpaceTrait, creature: impl SpaceTrait) {
}
// INCORRECT DISUGARED VERSION
fn compare_attributes<T: SpaceTrait>(creature1: T, creature: T) {
}
let human = SpaceHuman {..};
let dog = SpaceDog {..};
let result = compare_attributes(dog , human); // This will fail because they are of separate concrete types.
```
The above will fail because dog is not human and vice versa, despite them sharing traits.
Therefore, it makes sense that Rust would err on the side of making them have different concrete types.
i.e
```rs
fn compare_attributes(creature1: impl SpaceTrait, creature: impl SpaceTrait) {
}
// CORRECT DISUGARED VERSION.
// notice the different aarguments?
fn compare_attributes<T: SpaceTrait, U: SpaceTrait>(creature1: T, creature: U) {
}
// can also be written as:
fn compare_attributes<T: SpaceTrait, U: SpaceTrait>(creature1: T, creature: U)
where
T: SpaceTrait,
U: SpaceTrait
{
}
let human = SpaceHuman {..};
let dog = SpaceDog {..};
let result = compare_attributes(dog , human); // This will now work.
```
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment