Skip to content

Commit

Permalink
Expand structs and traits
Browse files Browse the repository at this point in the history
  • Loading branch information
uellenberg committed May 23, 2024
1 parent 4f90159 commit 29056f2
Showing 1 changed file with 27 additions and 1 deletion.
28 changes: 27 additions & 1 deletion sections/00_rust_concepts/04_structs_and_traits/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -196,7 +196,33 @@ pub fn print_id(value: impl HasDataID) {
}
```

This special syntax (`impl HasDataID`) means that the function can take in any piece of data that implements `HasDataID`. So, we could put a `User` in and it would work just fine. Behind the scenes, this is using generics (which we'll get into later), and it actually creates a copy of the function for each different kind of data that gets put into it.
This special syntax (`impl HasDataID`) means that the function can take in any piece of data that implements `HasDataID`. So, we could put a `User` in and it would work just fine. Behind the scenes, this is using generics (which we'll get into later), and it actually creates a copy of the function for each different kind of data that gets put into it. You can also use this `impl Trait` syntax for the return value of a function, so that any code calling it **doesn't need to worry about how the data is structured, just what it can be used for**.

One of the best things about traits is that you can write functions based on the implementation of multiple traits by putting a `+` between them. Imagine if you have a `HasDataID` trait (like the one described above), as well as a `HasName` trait. With the `impl Trait` syntax, you can create functions that require both of these traits to be implemented:

```rust
pub fn print_name_id(value: impl HasDataID + HasName) {
println!("ID: {}", value.get_id());
println!("Name: {}", value.get_name());
}
```

Now, we can call this function with our `User` struct, but we could also have a new struct (say, `Lesson`) that would also work just fine with the function above (so long as it implements the `HasDataID` and `HasName` traits). This pattern of requiring multiple traits gives us superpowers: not only can we write a single piece of code to work with multiple different types of data (in a way that's even more powerful than what can be achieved in traditional object-oriented languages, as evidenced by the example above), but Rust libraries are also often designed to take advantage of this fact.

In many web servers, for example, you'll find functions that are capable of taking in hundreds of distinct data types almost like magic. An excellent example of this is this truncated example of `actix-web`:

```rust
async fn page(path: web::Path<(String, String)>) -> impl Responder {
let (a, b) = path.into_inner();
format!("You said {a} and {b}.")
}

// ...
App::new().route("/mypage/{a}/{b}", web::get().to(page))
// ...
```

When you compile your program, `actix-web` looks at the parameters specified in your request handler function (`page`) and uses them to figure out how to extract data from the web request and give them to you in whatever order or format you specify. You can add more parameters to request handler to take out different pieces of data and the web server will automatically give them to you. All of this is powered by Rust's trait system: functions in Rust implement a function trait, and the trait system is powerful enough to implement this sort of API that's incredibly easy to use.

## Conclusion

Expand Down

0 comments on commit 29056f2

Please sign in to comment.