diff --git a/solutions/generics-traits/trait-object.md b/solutions/generics-traits/trait-object.md new file mode 100644 index 0000000..2158326 --- /dev/null +++ b/solutions/generics-traits/trait-object.md @@ -0,0 +1,221 @@ +1. +```rust +trait Bird { + fn quack(&self) -> String; +} + +struct Duck; +impl Duck { + fn swim(&self) { + println!("Look, the duck is swimming") + } +} +struct Swan; +impl Swan { + fn fly(&self) { + println!("Look, the duck.. oh sorry, the swan is flying") + } +} + +impl Bird for Duck { + fn quack(&self) -> String{ + "duck duck".to_string() + } +} + +impl Bird for Swan { + fn quack(&self) -> String{ + "swan swan".to_string() + } +} + +fn main() { + let duck = Duck; + duck.swim(); + + let bird = hatch_a_bird(2); + // this bird has forgotten how to swim, so below line will cause an error + // bird.swim(); + // but it can quak + assert_eq!(bird.quack(), "duck duck"); + + let bird = hatch_a_bird(1); + // this bird has forgotten how to fly, so below line will cause an error + // bird.fly(); + // but it can quak too + assert_eq!(bird.quack(), "swan swan"); + + println!("Success!") +} + +fn hatch_a_bird(species: u8) ->Box { + if species == 1 { + Box::new(Swan{}) + } else { + Box::new(Duck{}) + } +} +``` + +2. +```rust +trait Bird { + fn quack(&self); +} + +struct Duck; +impl Duck { + fn fly(&self) { + println!("Look, the duck is flying") + } +} +struct Swan; +impl Swan { + fn fly(&self) { + println!("Look, the duck.. oh sorry, the swan is flying") + } +} + +impl Bird for Duck { + fn quack(&self) { + println!("{}", "duck duck"); + } +} + +impl Bird for Swan { + fn quack(&self) { + println!("{}", "swan swan"); + } +} + +fn main() { + let birds: [Box; 2] = [Box::new(Duck {}), Box::new(Swan {})]; + + for bird in birds { + bird.quack(); + // when duck and swan turns into Bird, they all forgot how to fly, only remeber how to quack + // so, the below code will cause an error + // bird.fly(); + } +} +``` + +3. +```rust +trait Draw { + fn draw(&self) -> String; +} + +impl Draw for u8 { + fn draw(&self) -> String { + format!("u8: {}", *self) + } +} + +impl Draw for f64 { + fn draw(&self) -> String { + format!("f64: {}", *self) + } +} + +fn main() { + let x = 1.1f64; + let y = 8u8; + + // draw x + draw_with_box(Box::new(x)); + + // draw y + draw_with_ref(&y); +} + +fn draw_with_box(x: Box) { + x.draw(); +} + +fn draw_with_ref(x: &dyn Draw) { + x.draw(); +} +``` + +4. +```rust +trait Foo { + fn method(&self) -> String; +} + +impl Foo for u8 { + fn method(&self) -> String { format!("u8: {}", *self) } +} + +impl Foo for String { + fn method(&self) -> String { format!("string: {}", *self) } +} + +// implement below with generics +fn static_dispatch(x: T) { + x.method(); +} + +// implement below with trait objects +fn dynamic_dispatch(x: &dyn Foo) { + x.method(); +} + +fn main() { + let x = 5u8; + let y = "Hello".to_string(); + + static_dispatch(x); + dynamic_dispatch(&y); + + println!("Success!") +} +``` + +5. +```rust +trait MyTrait { + fn f(&self) -> Self; +} + +impl MyTrait for u32 { + fn f(&self) -> u32 { 42 } +} + +impl MyTrait for String { + fn f(&self) -> String { self.clone() } +} + +fn my_function(x: impl MyTrait) -> impl MyTrait { + x.f() +} + +fn main() { + my_function(13_u32); + my_function(String::from("abc")); +} +``` + +```rust +trait MyTrait { + fn f(&self) -> Box; +} + +impl MyTrait for u32 { + fn f(&self) -> Box { Box::new(42) } +} + +impl MyTrait for String { + fn f(&self) -> Box { Box::new(self.clone()) } +} + +fn my_function(x: Box) -> Box { + x.f() +} + +fn main() { + my_function(Box::new(13_u32)); + my_function(Box::new(String::from("abc"))); +} +``` \ No newline at end of file diff --git a/src/generics-traits/trait-object.md b/src/generics-traits/trait-object.md index 38a0b34..986ef8a 100644 --- a/src/generics-traits/trait-object.md +++ b/src/generics-traits/trait-object.md @@ -1 +1,225 @@ # Trait Object +In [traits chapter](https://practice.rs/generics-traits/traits.html#returning-types-that-implement-traits) we have seen that we can't use `impl Trait` when returning multiple types. + +Also one limitation of arrays is that they can only store elements of one type, yeah, enum is a not bad solution when our items are a fixed set of types in compile time, but trait object are more flexible and powerful here. + +## Returning Traits with dyn +The Rust compiler needs to know how much space a function's return type requires. Because the different implementations of a trait probably will need different amounts of memoery, this means function need to return a concrete type or the same type when using `impl Trait`, or it can return a trait object with `dyn`. + +1. ๐ŸŒŸ๐ŸŒŸ๐ŸŒŸ +```rust,editable + +trait Bird { + fn quack(&self) -> String; +} + +struct Duck; +impl Duck { + fn swim(&self) { + println!("Look, the duck is swimming") + } +} +struct Swan; +impl Swan { + fn fly(&self) { + println!("Look, the duck.. oh sorry, the swan is flying") + } +} + +impl Bird for Duck { + fn quack(&self) -> String{ + "duck duck".to_string() + } +} + +impl Bird for Swan { + fn quack(&self) -> String{ + "swan swan".to_string() + } +} + +fn main() { + // FILL in the blank + let duck = __; + duck.swim(); + + let bird = hatch_a_bird(2); + // this bird has forgotten how to swim, so below line will cause an error + // bird.swim(); + // but it can quak + assert_eq!(bird.quack(), "duck duck"); + + let bird = hatch_a_bird(1); + // this bird has forgotten how to fly, so below line will cause an error + // bird.fly(); + // but it can quak too + assert_eq!(bird.quack(), "swan swan"); + + println!("Success!") +} + +// IMPLEMENT this function +fn hatch_a_bird... + +``` +## Array with trait objects +2. ๐ŸŒŸ๐ŸŒŸ +```rust,editable +trait Bird { + fn quack(&self); +} + +struct Duck; +impl Duck { + fn fly(&self) { + println!("Look, the duck is flying") + } +} +struct Swan; +impl Swan { + fn fly(&self) { + println!("Look, the duck.. oh sorry, the swan is flying") + } +} + +impl Bird for Duck { + fn quack(&self) { + println!("{}", "duck duck"); + } +} + +impl Bird for Swan { + fn quack(&self) { + println!("{}", "swan swan"); + } +} + +fn main() { + // FILL in the blank to make the code work + let birds __; + + for bird in birds { + bird.quack(); + // when duck and swan turns into Bird, they all forgot how to fly, only remeber how to quack + // so, the below code will cause an error + // bird.fly(); + } +} +``` + + +## `&dyn` and `Box` + +3. ๐ŸŒŸ๐ŸŒŸ +```rust,editable + +// FILL in the blanks +trait Draw { + fn draw(&self) -> String; +} + +impl Draw for u8 { + fn draw(&self) -> String { + format!("u8: {}", *self) + } +} + +impl Draw for f64 { + fn draw(&self) -> String { + format!("f64: {}", *self) + } +} + +fn main() { + let x = 1.1f64; + let y = 8u8; + + // draw x + draw_with_box(__); + + // draw y + draw_with_ref(&y); + + println!("Success!") +} + +fn draw_with_box(x: Box) { + x.draw(); +} + +fn draw_with_ref(x: __) { + x.draw(); +} +``` + +## Static and Dynamic dispatch +when we use trait bounds on generics: the compiler generates nongeneric implementations of functions and methods for each concrete type that we use in place of a generic type parameter. The code that results from monomorphization is doing static dispatch, which is when the compiler knows what method youโ€™re calling at compile time. + +When we use trait objects, Rust must use dynamic dispatch. The compiler doesnโ€™t know all the types that might be used with the code that is using trait objects, so it doesnโ€™t know which method implemented on which type to call. Instead, at runtime, Rust uses the pointers inside the trait object to know which method to call. There is a runtime cost when this lookup happens that doesnโ€™t occur with static dispatch. Dynamic dispatch also prevents the compiler from choosing to inline a methodโ€™s code, which in turn prevents some optimizations. + +However, we did get extra flexibility when using dynamic dispatch. + +4. ๐ŸŒŸ๐ŸŒŸ +```rust,editable + +trait Foo { + fn method(&self) -> String; +} + +impl Foo for u8 { + fn method(&self) -> String { format!("u8: {}", *self) } +} + +impl Foo for String { + fn method(&self) -> String { format!("string: {}", *self) } +} + +// IMPLEMENT below with generics +fn static_dispatch... + +// implement below with trait objects +fn dynamic_dispatch... + +fn main() { + let x = 5u8; + let y = "Hello".to_string(); + + static_dispatch(x); + dynamic_dispatch(&y); + + println!("Success!") +} +``` + +## Object safe +You can only make object-safe traits into trait objects. A trait is object safe if all the methods defined in the trait have the following properties: + +- The return type isnโ€™t `Self`. +- There are no generic type parameters. + +5. ๐ŸŒŸ๐ŸŒŸ๐ŸŒŸ๐ŸŒŸ +```rust,editable + +// Use at least two approaches to make it work +// DON'T add/remove any code line +trait MyTrait { + fn f(&self) -> Self; +} + +impl MyTrait for u32 { + fn f(&self) -> Self { 42 } +} + +impl MyTrait for String { + fn f(&self) -> Self { self.clone() } +} + +fn my_function(x: Box) { + x.f() +} + +fn main() { + my_function(Box::new(13_u32)); + my_function(Box::new(String::from("abc"))); +} +``` \ No newline at end of file