Skip to content

Commit

Permalink
feat: update async and traits (in prep to Rust 1.75); update traits /…
Browse files Browse the repository at this point in the history
… trait objects
  • Loading branch information
john-cd committed Dec 22, 2023
1 parent 1423bb1 commit 1bc0bf3
Show file tree
Hide file tree
Showing 5 changed files with 193 additions and 64 deletions.
1 change: 1 addition & 0 deletions src/SUMMARY.md
Original file line number Diff line number Diff line change
Expand Up @@ -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)
Expand Down
47 changes: 40 additions & 7 deletions src/lang/trait_objects.md
Original file line number Diff line number Diff line change
Expand Up @@ -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<dyn ClickCallback>` 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<dyn ClickCallback>` 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<Box<dyn Draw>>, // 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<Box<dyn Draw>>, // <-- 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 )
119 changes: 89 additions & 30 deletions src/lang/traits.md
Original file line number Diff line number Diff line change
Expand Up @@ -12,27 +12,31 @@ 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;
fn summarize(&self) -> String { // default implementation
format!("(Read more from {}...)", self.summarize_author()) // can call a non-default method
}
}

fn main() {}
```

## Supertraits
Expand All @@ -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;
Expand All @@ -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<T: Summary>(item: &T) {
// Trait bound syntax (mostly equivalent):
pub fn notify2<T: Summary>(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, U>(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<T> {
fn test(t: T);
}
impl<T, U> Test<T> for U {
impl<T, U> Test<T> 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<Item = u32>`
fn next(&mut self) -> Option<Self::Item>;
}

fn main() {}
```

```rust
trait Add<Rhs=Self> { // default generic type
```rust,ignore
trait Add<Rhs=Self> { // 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: Hash>(t: &T) {
println!("The hash is {}", t.hash())
}
struct Pair<A, B> { 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<A: Hash, B: Hash> Hash for Pair<A, B> {
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 )
27 changes: 0 additions & 27 deletions src/topics/async.md
Original file line number Diff line number Diff line change
Expand Up @@ -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;
}
}
```
63 changes: 63 additions & 0 deletions src/topics/async_traits.md
Original file line number Diff line number Diff line change
@@ -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<Item = Widget>;
}
impl Container for MyContainer {
fn items(&self) -> impl Iterator<Item = Widget> {
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;
}
}
```

0 comments on commit 1bc0bf3

Please sign in to comment.