From 1bc0bf3222c69b168a68c0ee5b978e0052d22b99 Mon Sep 17 00:00:00 2001 From: S John CD Date: Fri, 22 Dec 2023 02:58:39 +0000 Subject: [PATCH] feat: update async and traits (in prep to Rust 1.75); update traits / trait objects --- src/SUMMARY.md | 1 + src/lang/trait_objects.md | 47 ++++++++++++--- src/lang/traits.md | 119 +++++++++++++++++++++++++++---------- src/topics/async.md | 27 --------- src/topics/async_traits.md | 63 ++++++++++++++++++++ 5 files changed, 193 insertions(+), 64 deletions(-) create mode 100644 src/topics/async_traits.md diff --git a/src/SUMMARY.md b/src/SUMMARY.md index ce69de86..f75c3036 100644 --- a/src/SUMMARY.md +++ b/src/SUMMARY.md @@ -48,6 +48,7 @@ - [Shared-state concurrency](topics/shared_state.md) - [Concurrent data structures](topics/concurrent_data_structures.md) - [Async](topics/async.md) + - [Async and traits](topics/async_traits.md) - [Tokio](topics/tokio.md) - [Async channels](topics/async_channels.md) - [Streams](topics/streams.md) diff --git a/src/lang/trait_objects.md b/src/lang/trait_objects.md index a68780c9..f8592cd2 100644 --- a/src/lang/trait_objects.md +++ b/src/lang/trait_objects.md @@ -2,28 +2,61 @@ In Rust, traits are types, but they are "unsized", which roughly means that they are only allowed to show up behind a pointer like `Box` (which points onto the heap) or `&` (which can point anywhere). -A type like `&ClickCallback` or `Box` where `ClickCallback` is a Trait is called a "trait object", and includes a pointer to an instance of a type T implementing ClickCallback, and a vtable: a pointer to T's implementation of each method in the trait +A type like `&ClickCallback` or `Box` where `ClickCallback` is a Trait is called a "trait object", and includes a pointer to an instance of a type `T` implementing `ClickCallback`, and a vtable: a pointer to `T`'s implementation of each method in the trait. ```rust -pub trait Draw { +trait Draw { fn draw(&self); } -pub struct Screen { - pub components: Vec>, // trait object +struct Button; + +impl Draw for Button { + fn draw(&self) { println!("Button"); } +} + +struct Text; + +impl Draw for Text { + fn draw(&self) { println!("Text"); } +} + +struct Screen { + components: Vec>, // <-- trait object } impl Screen { - pub fn run(&self) { + + fn new() -> Self { + Screen { components: vec![Box::new(Button), Box::new(Text), Box::new(Text)] } + } + + fn run(&self) { for component in self.components.iter() { + // The purpose of trait objects is to permit "late binding" of methods. + // Calling a method on a trait object results in virtual dispatch at runtime. + // Here, `components` is a mix of `Button` and `Text` structs. component.draw(); + } } } -fn main() {} +fn main() { + let s = Screen::new(); + s.run(); +} +``` + +The set of traits after `dyn` is made up of an [object-safe]( https://doc.rust-lang.org/nightly/reference/items/traits.html#object-safety ) base trait plus any number of autotraits (one of `Send`, `Sync`, `Unpin`, `UnwindSafe`, and `RefUnwindSafe` - see [special traits]( https://doc.rust-lang.org/nightly/reference/special-types-and-traits.html )). + +```rust,ignore +dyn Trait +dyn Trait + Send +dyn Trait + Send + Sync +dyn Trait + 'static ``` ## See also -[Trait Objects]( https://doc.rust-lang.org/book/ch17-02-trait-objects.html ) +[Trait Objects (docs)]( https://doc.rust-lang.org/book/ch17-02-trait-objects.html ) diff --git a/src/lang/traits.md b/src/lang/traits.md index 10a45e97..1704ba67 100644 --- a/src/lang/traits.md +++ b/src/lang/traits.md @@ -12,18 +12,24 @@ pub struct NewsArticle { pub content: String, } -impl Summary for NewsArticle { // implement Trait on a Type +// Implement Trait on a Type +impl Summary for NewsArticle { fn summarize(&self) -> String { format!("{}, by {} ({})", self.headline, self.author, self.location) } } -fn main() {} +fn main() { + let na = NewsArticle { headline: "headline".to_string(), location: "location".to_string(), author: "author".to_string(), content: "...".to_string()}; + println!("Summary: {}", na.summarize()); +} ``` +Trait methods are in scope only when their trait is. + ## Default implementation -```rust +```rust,ignore pub trait Summary { fn summarize_author(&self) -> String; @@ -31,8 +37,6 @@ pub trait Summary { format!("(Read more from {}...)", self.summarize_author()) // can call a non-default method } } - -fn main() {} ``` ## Supertraits @@ -42,16 +46,33 @@ use std::fmt; trait OutlinePrint: fmt::Display { fn outline_print(&self) { - println!("* {} *", self); // can use println! because self is guaranteed to implement Display + println!("* {} *", self); // can use println! because self is guaranteed to implement Display } } -fn main() {} +// String implements Display. That would not work otherwise. +impl OutlinePrint for String {} + +fn main() { String::from("test").outline_print(); } ``` ## Newtype pattern -Unlike interfaces in languages like Java, C# or Scala, new traits can be implemented for existing types. One restriction to note is that we can implement a trait on a type only if at least one of the trait or the type is local to our crate. If neither are, use the Newtype pattern: +Unlike interfaces in languages like Java, C# or Scala, new traits can be implemented for _existing_ types. + +```rust,ignore +trait MyHash { + fn hash(&self) -> u64; +} + +impl MyHash for i64 { + fn hash(&self) -> u64 { + *self as u64 + } +} +``` + +One restriction to note is that we can implement a trait on a type only if at least one of the trait or the type is local to our crate. If neither are, use the Newtype pattern: ```rust use std::fmt; @@ -65,75 +86,113 @@ impl fmt::Display for Wrapper { } // If we wanted the new type to have every method the inner type has, implement the `Deref` trait. -fn main() {} +fn main() { println!("{}", Wrapper(vec!["example".to_string(), "example 2".to_string()])); } ``` ## Trait as parameter ```rust,ignore -pub fn notify(item: &impl Summary) { // accepts any type that implements the specified trait. +// Accepts any type that implements the specified trait: +pub fn notify(item: &impl Summary) { println!("Breaking news! {}", item.summarize()); } -// or trait bound syntax: -pub fn notify(item: &T) { +// Trait bound syntax (mostly equivalent): +pub fn notify2(item: &T) { println!("Breaking news! {}", item.summarize()); } - -fn main() {} ``` ## Multiple traits ```rust,ignore -pub fn notify(item: &(impl Summary + Display)) { } +// Note the `+` +fn notify(item: &(impl Summary + Display)) { } fn some_function(t: &T, u: &U) -> i32 where - T: Display + Clone, // note the + + T: Display + Clone, // note the `+` U: Clone + Debug, -{ +{ } +``` + +## Return-position impl Trait -fn main() {} +```rust +fn returns_closure() -> impl Fn(i32) -> i32 { + |x| x + 1 +} + +fn main() { + let f = returns_closure(); + println!("{}", f(1)); +} ``` ## Generic traits -```rust +```rust,ignore trait Test { fn test(t: T); } -impl Test for U { +impl Test for U { // note the <> in two places fn test(t: T) {} } - -fn main() {} ``` ## Associated types -```rust +```rust,ignore pub trait Iterator { - type Item; // im mpl, use e.g. type Item = u32; + type Item; // in Impl, use e.g. `Iterator` fn next(&mut self) -> Option; } - -fn main() {} ``` -```rust -trait Add { // default generic type +```rust,ignore +trait Add { // default generic type type Output; fn add(self, rhs: Rhs) -> Self::Output; } +``` + +## Trait bounds + +```rust,ignore +// Trait bounds: the `print_hash` function is generic over an unknown type `T`, +// but requires that `T` implements the `Hash` trait. +fn print_hash(t: &T) { + println!("The hash is {}", t.hash()) +} + +struct Pair { first: A, second: B } -fn main() {} +// Generics make it possible to implement a trait conditionally +// Here, the Pair type implements Hash if, and only if, its components do +impl Hash for Pair { + fn hash(&self) -> u64 { + self.first.hash() ^ self.second.hash() + } +} ``` +## Constants in traits + +```rust,ignore +trait Example { + const CONST_NO_DEFAULT: i32; + const CONST_WITH_DEFAULT: i32 = 99; +} +``` + +## Async and traits + +See [Async](../topics/async.md) + ## See also -[Traits]( https://blog.rust-lang.org/2015/05/11/traits.html ) +[Traits (blog)]( https://blog.rust-lang.org/2015/05/11/traits.html ) diff --git a/src/topics/async.md b/src/topics/async.md index 92ccd95e..7ca512b7 100644 --- a/src/topics/async.md +++ b/src/topics/async.md @@ -94,30 +94,3 @@ Alternatives to the Tokio async ecosystem include: - [Smol]( https://crates.io/crates/smol ) - [Embassy]( https://embassy.dev/ ) for embedded systems. - [Mio]( https://crates.io/crates/mio ) is a fast, low-level I/O library for Rust focusing on non-blocking APIs and event notification for building high performance I/O apps with as little overhead as possible over the OS abstractions. It is part of the Tokio ecosystem. - -## Async traits - -The async working group's headline goal for 2023 is to stabilize a "minimum viable product" (MVP) version of async functions in traits. -In the meanwhile, use the [Async trait crate]( https://github.com/dtolnay/async-trait ). - -```rust,ignore -use async_trait::async_trait; - -#[async_trait] -trait Advertisement { - async fn run(&self); -} - -struct Modal; - -#[async_trait] -impl Advertisement for Modal { - async fn run(&self) { - self.render_fullscreen().await; - for _ in 0..4u16 { - remind_user_to_join_mailing_list().await; - } - self.hide_for_now().await; - } -} -``` diff --git a/src/topics/async_traits.md b/src/topics/async_traits.md new file mode 100644 index 00000000..f77ea1e0 --- /dev/null +++ b/src/topics/async_traits.md @@ -0,0 +1,63 @@ +# Async traits + +As of Rust 1.75, it is possible to have `async` functions in traits: + +```rust,ignore +trait HealthCheck { + async fn check(&mut self) -> bool; // <- async fn defined in a Trait +} + +impl HealthCheck for MyHealthChecker { + async fn check(&mut self) -> bool { // async fn implementation in the associated impl block + do_async_op().await + } +} + +async fn do_health_check(hc: impl HealthCheck) { + if !hc.check().await { // use as normal + log_health_check_failure().await; + } +} +``` + +[Stabilizing async fn in traits in 2023]( https://blog.rust-lang.org/inside-rust/2023/05/03/stabilizing-async-fn-in-trait.html ) + +This is in turn enabled by return-position `impl Trait` in traits, since `async fn` is sugar for functions that return `-> impl Future`. + +```rust,ignore +trait Container { + fn items(&self) -> impl Iterator; +} + +impl Container for MyContainer { + fn items(&self) -> impl Iterator { + self.items.iter().cloned() + } +} +``` + +Note that there are still caveats for public traits - see [Announcing `async fn` and return-position `impl Trait` in traits]( https://blog.rust-lang.org/2023/12/21/async-fn-rpit-in-traits.html# ). + +In addition, traits that use `-> impl Trait` and `async fn` are not object-safe, which means they lack support for dynamic dispatch. In the meanwhile, use the [Async trait crate]( https://github.com/dtolnay/async-trait ). + +```rust,ignore +use async_trait::async_trait; + +#[async_trait] +trait Advertisement { + async fn run(&self); +} + +struct Modal; + +#[async_trait] +impl Advertisement for Modal { + async fn run(&self) { + self.render_fullscreen().await; + for _ in 0..4u16 { + remind_user_to_join_mailing_list().await; + } + self.hide_for_now().await; + } +} +```