Skip to content

Commit dc29950

Browse files
committed
05: discussion on generics + lifetimes + traits
1 parent 172ec24 commit dc29950

File tree

6 files changed

+298
-1
lines changed

6 files changed

+298
-1
lines changed

Cargo.toml

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -41,6 +41,18 @@ path = "content/lessons/03_data_types/result.rs"
4141
name = "05_basic_traits"
4242
path = "content/lessons/05_types_reasoning/basic_trait.rs"
4343
[[bin]]
44+
name = "05_basic_trait_display"
45+
path = "content/lessons/05_types_reasoning/basic_trait_display.rs"
46+
[[bin]]
47+
name = "05_trait_associated_types"
48+
path = "content/lessons/05_types_reasoning/trait_associated_type.rs"
49+
[[bin]]
50+
name = "05_trait_generic_types"
51+
path = "content/lessons/05_types_reasoning/trait_generic_type.rs"
52+
[[bin]]
53+
name = "05_impl_trait"
54+
path = "content/lessons/05_types_reasoning/impl_trait.rs"
55+
[[bin]]
4456
name = "05_generic_largest"
4557
path = "content/lessons/05_types_reasoning/generic_largest.rs"
4658
[[bin]]
Lines changed: 36 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,36 @@
1+
#![allow(dead_code)]
2+
3+
use std::fmt::Display;
4+
5+
struct NewsArticle {
6+
headline: String,
7+
location: String,
8+
author: String,
9+
content: String,
10+
}
11+
12+
impl Display for NewsArticle {
13+
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
14+
write!(f, "{}, by {} ({})", self.headline, self.author, self.location)
15+
}
16+
}
17+
18+
struct Tweet {
19+
username: String,
20+
content: String,
21+
}
22+
23+
impl Display for Tweet {
24+
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
25+
write!(f, "{}: {}", self.username, self.content)
26+
}
27+
}
28+
29+
fn main() {
30+
let tweet = Tweet {
31+
username: String::from("horse_ebooks"),
32+
content: String::from("of course, as you probably already know, people"),
33+
};
34+
35+
println!("1 new tweet: {}", tweet);
36+
}
Lines changed: 65 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,65 @@
1+
#![allow(dead_code)]
2+
3+
use std::fmt::Display;
4+
5+
trait Summary {
6+
fn summarize(&self) -> impl Display;
7+
}
8+
9+
struct NewsArticle {
10+
headline: String,
11+
location: String,
12+
author: String,
13+
content: String,
14+
}
15+
16+
impl Display for NewsArticle {
17+
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
18+
writeln!(f, "{}, by {} ({})", self.headline, self.author, self.location)?;
19+
f.write_str(&self.content)
20+
}
21+
}
22+
23+
struct NewsArticleSummarizer<'a>(&'a NewsArticle);
24+
25+
impl Display for NewsArticleSummarizer<'_> {
26+
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
27+
let article = self.0;
28+
write!(f, "{}, by {} ({})", article.headline, article.author, article.location)
29+
}
30+
}
31+
32+
impl Summary for NewsArticle {
33+
fn summarize(&self) -> impl Display {
34+
NewsArticleSummarizer(self)
35+
}
36+
}
37+
38+
struct Tweet {
39+
username: String,
40+
content: String,
41+
}
42+
43+
impl Summary for Tweet {
44+
fn summarize(&self) -> impl Display {
45+
struct TweetSummarizer<'a>(&'a Tweet);
46+
47+
impl Display for TweetSummarizer<'_> {
48+
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
49+
let tweet = self.0;
50+
write!(f, "{}: {}", tweet.username, tweet.content)
51+
}
52+
}
53+
54+
TweetSummarizer(self)
55+
}
56+
}
57+
58+
fn main() {
59+
let tweet = Tweet {
60+
username: String::from("horse_ebooks"),
61+
content: String::from("of course, as you probably already know, people"),
62+
};
63+
64+
println!("1 new tweet: {}", tweet.summarize());
65+
}

content/lessons/05_types_reasoning/index.md

Lines changed: 34 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
+++
22
title = "Reasoning About Types"
3-
date = 2024-10-23
3+
date = 2024-10-24
44
weight = 1
55
[extra]
66
lesson_date = 2024-10-24
@@ -321,6 +321,39 @@ There exists one special lifetime called `'static`, which means that a reference
321321
let s: &'static str = "I have a static lifetime.";
322322
```
323323

324+
# Trait + lifetimes - a challenging tandem
325+
326+
Let's go back to our `basic_trait.rs` example. The `Summary` trait was really wasteful: it always allocated the `String`s on heap, even though we only needed to display the formatted string, and we could do that without allocations. How? By using `Display` trait, of course.
327+
328+
The simplest possible optimisation would be like this:
329+
330+
{{ include_code_sample(path="lessons/05_types_reasoning/basic_trait_display.rs", language="rust") }}
331+
332+
This eliminates the heap allocations, but there's another catch. What if `NewsArticle` already had another (non-summarizing) `Display` implementation? We would end up in a double-trait-implementation conflict, which is a compile-time error.
333+
334+
We can solve the one-type-one-trait-impl problem by introducing another type just for summarizing. The first attempt could be to use generics in traits:
335+
336+
{{ include_code_sample(path="lessons/05_types_reasoning/trait_generic_type.rs", language="rust") }}
337+
338+
The problem here is that nothing hinders us from implement the trait (with various type parameters) for the same type, which leads to awkward ambiguity when calling the trait's methods (see `main` fn).
339+
340+
The use of generic types in `Summary` trait makes it semantics like this:
341+
342+
> Any type can be summarized with any type supporting it.
343+
344+
When we want the trait to require exactly one possible generic implementation for a given type, we can leverage *associated types*. Example here:
345+
346+
{{ include_code_sample(path="lessons/05_types_reasoning/trait_associated_type.rs", language="rust") }}
347+
348+
The use of associated types in Summary trait makes it semantics like this:
349+
350+
> Any type can be summarized with at most one specific type.
351+
352+
Yet another approach (arguably, the cleanest one) would be to use the `impl trait` syntax in a trait (quite recently stabilized!).
353+
Example:
354+
355+
{{ include_code_sample(path="lessons/05_types_reasoning/impl_trait.rs", language="rust") }}
356+
324357
# Obligatory reading
325358

326359
- [The Book, chapter 10](https://doc.rust-lang.org/book/ch10-00-generics.html)
Lines changed: 69 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,69 @@
1+
#![allow(dead_code)]
2+
3+
use std::fmt::Display;
4+
5+
trait Summary<'a> {
6+
type Summarizer: Display;
7+
8+
fn summarize(&'a self) -> Self::Summarizer;
9+
}
10+
11+
struct NewsArticle {
12+
headline: String,
13+
location: String,
14+
author: String,
15+
content: String,
16+
}
17+
18+
impl Display for NewsArticle {
19+
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
20+
writeln!(f, "{}, by {} ({})", self.headline, self.author, self.location)?;
21+
f.write_str(&self.content)
22+
}
23+
}
24+
25+
struct NewsArticleSummarizer<'a>(&'a NewsArticle);
26+
27+
impl Display for NewsArticleSummarizer<'_> {
28+
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
29+
let article = self.0;
30+
write!(f, "{}, by {} ({})", article.headline, article.author, article.location)
31+
}
32+
}
33+
34+
impl<'a> Summary<'a> for NewsArticle {
35+
type Summarizer = NewsArticleSummarizer<'a>;
36+
fn summarize(&'a self) -> Self::Summarizer {
37+
NewsArticleSummarizer(self)
38+
}
39+
}
40+
41+
struct Tweet {
42+
username: String,
43+
content: String,
44+
}
45+
46+
struct TweetSummarizer<'a>(&'a Tweet);
47+
48+
impl Display for TweetSummarizer<'_> {
49+
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
50+
let tweet = self.0;
51+
write!(f, "{}: {}", tweet.username, tweet.content)
52+
}
53+
}
54+
55+
impl<'a> Summary<'a> for Tweet {
56+
type Summarizer = TweetSummarizer<'a>;
57+
fn summarize(&'a self) -> Self::Summarizer {
58+
TweetSummarizer(self)
59+
}
60+
}
61+
62+
fn main() {
63+
let tweet = Tweet {
64+
username: String::from("horse_ebooks"),
65+
content: String::from("of course, as you probably already know, people"),
66+
};
67+
68+
println!("1 new tweet: {}", tweet.summarize());
69+
}
Lines changed: 82 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,82 @@
1+
#![allow(dead_code)]
2+
3+
use std::fmt::Display;
4+
5+
trait Summary<'a, Summarizer: Display> {
6+
fn summarize(&'a self) -> Summarizer;
7+
}
8+
9+
struct NewsArticle {
10+
headline: String,
11+
location: String,
12+
author: String,
13+
content: String,
14+
}
15+
16+
impl Display for NewsArticle {
17+
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
18+
writeln!(f, "{}, by {} ({})", self.headline, self.author, self.location)?;
19+
f.write_str(&self.content)
20+
}
21+
}
22+
23+
struct NewsArticleSummarizer<'a>(&'a NewsArticle);
24+
25+
impl Display for NewsArticleSummarizer<'_> {
26+
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
27+
let article = self.0;
28+
write!(f, "{}, by {} ({})", article.headline, article.author, article.location)
29+
}
30+
}
31+
32+
impl<'a> Summary<'a, NewsArticleSummarizer<'a>> for NewsArticle {
33+
fn summarize(&'a self) -> NewsArticleSummarizer<'a> {
34+
NewsArticleSummarizer(self)
35+
}
36+
}
37+
38+
struct Tweet {
39+
username: String,
40+
content: String,
41+
}
42+
43+
struct TweetSummarizer<'a>(&'a Tweet);
44+
45+
impl Display for TweetSummarizer<'_> {
46+
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
47+
let tweet = self.0;
48+
write!(f, "{}: {}", tweet.username, tweet.content)
49+
}
50+
}
51+
52+
impl<'a> Summary<'a, TweetSummarizer<'a>> for Tweet {
53+
fn summarize(&'a self) -> TweetSummarizer<'a> {
54+
TweetSummarizer(self)
55+
}
56+
}
57+
58+
impl<'a> Summary<'a, NewsArticleSummarizer<'a>> for Tweet {
59+
fn summarize(&'a self) -> NewsArticleSummarizer<'a> {
60+
unimplemented!("This is only to make code type-check and compile.");
61+
}
62+
}
63+
64+
fn main() {
65+
let empty_article = NewsArticle {
66+
headline: "".into(),
67+
location: String::new(),
68+
author: String::default(),
69+
content: Default::default(),
70+
};
71+
println!("1 new article: {}", empty_article.summarize());
72+
73+
let tweet = Tweet {
74+
username: String::from("horse_ebooks"),
75+
content: String::from("of course, as you probably already know, people"),
76+
};
77+
78+
// Compile error: `type annotations needed; multiple `impl`s satisfying `Tweet: Summary<'_, _>` found`
79+
// println!("1 new tweet: {}", tweet.summarize());
80+
println!("1 new tweet: {}", <Tweet as Summary<'_, TweetSummarizer>>::summarize(&tweet));
81+
println!("1 new tweet: {}", <Tweet as Summary<'_, NewsArticleSummarizer>>::summarize(&tweet));
82+
}

0 commit comments

Comments
 (0)