I am a Java, C#, C or C++ developer, time to do some Rust

As I've said before, I'm working on a book about lifetimes. Or maybe it's just a long series - I haven't decided the specifics yet. Like every one of my series/book things, it's long, and it starts you off way in the periphery of the subject, and takes a lot of detours to get there.

In other words - it's great if you want an adventure (which truly understanding Rust definitely is), but it's not the best if you are currently on the puzzled end of a conversation with your neighborhood lifetime enforcer, the Rust compiler.

So, let's try to tackle that the crux of the issue another way - hopefully a more direct one.

I want to build an app

Let's say I want to build an app. Let's also say maybe I have some experience with another language - maybe it's Java, maybe it's C#, maybe it's C or C++, or maybe something else entirely.

Shell session
$ cargo new an-app Created binary (application) `some-app` package

Let's say it's a graphical app, which has a window, and the window should have a size. I could just do something like this:

Rust code
// in `src/main.rs` const WIDTH: usize = 1280; const HEIGHT: usize = 720; fn main() { println!("Should make a {}x{} window.", WIDTH, HEIGHT); }

That would do the job:

Shell session
$ cargo run --quiet Should make a 1280x720 window.

But if I've learned one thing being a Java/C#/C/C++/etc. developer, it's that globals are bad, and I shouldn't use them.

So instead - since I'm making an app - I can make an App struct:

Rust code
struct App { width: usize, height: usize, } fn main() { let app = App { width: 1280, height: 720, }; println!("Should make a {}x{} window.", app.width, app.height); }

This instantly feels a lot better. It's nicer to look at - and it groups together related values. It's not just "two globals" floating around the source code, it's part of the app - the app has a window size.

Let's also say the app's window will need its own title, because I feel strongly that people should use my app windowed, and not fullscreen - so they'll definitely see the titlebar.

So, I look up rust string and I find out that there is a type named String - this feels familiar. Java also has a type named String. So does C# - it even has the string keyword as an alias to it. C has, uh, unresolved issues, and C++ has a whole bunch of string types.

Rust code
struct App { width: usize, height: usize, title: String, } fn main() { let app = App { width: 1280, height: 720, title: "My app", }; println!( "Should make a {}x{} window with title {}", app.width, app.height, app.title ); }

Unfortunately, it doesn't work:

Shell session
cargo check --quiet error[E0308]: mismatched types --> src/main.rs:11:16 | 11 | title: "My app", | ^^^^^^^^ | | | expected struct `std::string::String`, found `&str` | help: try using a conversion method: `"My app".to_string()` error: aborting due to previous error

But I'm not deterred, because while I was putting off learning Rust, I've heard lots of people say: "you'll see, it's hard, but the compiler has got your back, so just go with the flow".

So, I just trust the compiler, and I do what it wants me to do:

Rust code
let app = App { width: 1280, height: 720, // new: title: "My app".to_string(), }; }
Shell session
$ cargo run --quiet Should make a 1280x720 window with title My app

So far, so good.

At this point in my process of building the app, I realize that now we have three fields, and two of them really seem like they belong together: the app I'm making is going to have lots of graphical elements, some of which are definitely going to have a width and a height.

Since making my first struct went okay, I just make another one:

Rust code
struct App { dimensions: Dimensions, title: String, } struct Dimensions { width: usize, height: usize, } fn main() { let app = App { dimensions: Dimensions { width: 1280, height: 720, }, title: "My app".to_string(), }; println!( "Should make a {}x{} window with title {}", app.dimensions.width, app.dimensions.height, app.title ); }

Now, keep in mind this is my first Rust project. A graphical application.

Because if I'm starting to learn Rust, it might as well involve a project I'm actually interested in. And I know it sounds ambitious, but I've definitely made a bunch of graphical applications before, in Java, C#, C or C++, so it's "just" a matter of figuring out the little differences Rust has, which should be no trouble at all.

If I was more advanced in Rust, I might be tempted to implement the Display or the Debug trait on Dimensions, because I expect a lot of print debugging, and the println! line is getting a bit long.

But right now, I'm blissfully unaware what a trait even is (can't they just call it a class? or an interface?), so I'm sticking with that code, which has the merit of being very explicit about what it does.

Now for the app itself

Although I'm happy that I got over my first compiler error, there is a lot to do. I'm able to actually create a window pretty quickly (omitted here for brevity) by looking up a "crate" (can't they just call it a library?) that does that, and adapting the code from its README.

I have to say, cargo is nice. I don't know how I feel about rustc yet, but cargo is nice. I sure wish Java, C#, C, or C++ had something like that.

Cool bear's hot tip

Don't they all have something like that?

They have some things, yes, but not like that.

But now that I have a window up and running, I'm starting to think about the logic of my application. How will it work?

If this was Java, C#, C or C++, I know exactly what I would do. I would have App take care of window creation, maybe keyboard, mouse and gamepad input, just all your generic, run-of-the-mill setup and bookkeeping operations, and then I would have all the project-specific logic somewhere else, in another class (or struct).

I've done that tons of times before. In fact, I already have a framework for doing that, that I've written myself - in Java, C#, C or C++, that lets me skip the boring setup and bookkeeping part, and concentrate on the logic.

In my Java, C# or C++ framework, I have a base class, Client, which has methods like update and render - they do nothing by default. But when I subclass it, for each of my projects, I just need to override those update and render methods to do what the project actually needs to do.

And my App class, well - I re-use that one everywhere. It contains a reference (Java/C#) or a pointer (C++), or a pointer to the data + a pointer to a struct full of function pointers (C), and whenever the App decides it's time to update, it calls this.client.update(...), and it ends up using Project47::update - which has the logic for project 47, rather than Client::update, which just does nothing.

Cool bear's hot tip

Aren't you going to include code examples in Java, C#, C or C++?

No, I'm not - because either the reader knows exactly what I'm talking about, from years of having to write Java, C#, C, or C++, or they don't, in which case their mind is fresh, and they should be reading some other article, that approaches that problem from another angle.

Cool bear's hot tip

So, what, should they just stop reading that article? They're already 6 minutes in.

Not necessarily - I will show Rust code that "simulates" how things would work if they were writing Java, C#, C or C++.

So.

With my prior experience in mind, I do a little bit of research to see how I could achieve the same thing, but in Rust. I'm not particularly pleased to discover that Rust does not have classes.

That means I now have two things to learn: lifetimes (which Rust advocates have been very vocal about), and traits.

Since this all seems intimidating, before I move on to building a framework so that I can make graphical apps without all the boilerplate, I try to do it the simple way - by just stuffing logic directly into the App.

I also decide that my first graphical app will actually not be graphical, it will simply output lines to the terminal, so that for the time being, I don't have to worry about whether I should use OpenGL, or Vulkan, or wgpu, or maybe I should make my own abstraction top of Metal for the macOS/iOS builds, I really like DirectX 12 though, maybe there's a translation layer from that to something else?

I'll worry about that later - for now I comment out the windowing code, and just focus on making a simple app that works in the terminal.

It'll be a "jack in the box" game - where you turn and turn and turn the crank, and then Jack just POPS OUT.

Using my prior knowledge, I write what seems like it should work, given my prior knowledge of Java, C# or C++ (it would be a bit more involved in C):

Rust code
// THE FOLLOWING CODE DOES NOT COMPILE // (and is overall quite different from valid Rust code) use std::{time::Duration, thread::sleep}; fn main() { let app = App { title: "Jack in the box".to_string(), ticks_left: 4, running: true, }; println!("=== You are now playing {} ===", app.title); loop { app.update(); app.render(); if !app.running { break; } sleep(Duration::from_secs(1)); } } struct App { title: String, ticks_left: usize, running: bool, fn update() { this.ticks_left -= 1; if this.ticks_left == 0 { this.running = false; } } fn render() { if this.ticks_left > 0 { println!("You turn the crank..."); } else { println!("Jack POPS OUT OF THE BOX"); } } }

This doesn't work at all. The Rust compiler is angry at me for a bunch of reasons. Or maybe it's just disappointed.

Shell session
$ cargo check --quiet error: expected identifier, found keyword `fn` --> src/main.rs:28:5 | 28 | fn update() { | ^^ expected identifier, found keyword error: expected `:`, found `update` --> src/main.rs:28:8 | 28 | fn update() { | ^^^^^^ expected `:` error[E0599]: no method named `update` found for struct `App` in the current scope --> src/main.rs:13:13 | 13 | app.update(); | ^^^^^^ method not found in `App` ... 23 | struct App { | ---------- method `update` not found for this error[E0599]: no method named `render` found for struct `App` in the current scope --> src/main.rs:14:13 | 14 | app.render(); | ^^^^^^ method not found in `App` ... 23 | struct App { | ---------- method `render` not found for this error: aborting due to 4 previous errors

Forced to go Back Online to research some of this, I discover that unlike in Java, C#, or C++, I can't just implement methods in the same block as the struct class block that contains the fields.

Weird choice, but okay - apparently what I need is an impl block:

Rust code
struct App { title: String, ticks_left: usize, running: bool, } impl App { fn update() { this.ticks_left -= 1; if this.ticks_left == 0 { this.running = false; } } fn render() { if this.ticks_left > 0 { println!("You turn the crank..."); } else { println!("Jack POPS OUT OF THE BOX"); } } }

This is apparently not enough to make my example work (it looks good to me, though) - now it's complaining about this:

Rust code
cargo check --quiet error[E0425]: cannot find value `this` in this scope --> src/main.rs:31:9 | 31 | this.ticks_left -= 1; | ^^^^ not found in this scope (cut)

A few google searches later, I learn that, unlike in Java, C# or C++, there is no concept of "static" and "non-static" methods. (The static keyword does exist, but it only has one of the meanings it has in C++).

However, update and render as I've written them are actually the closest thing to a "static method". I learn that, in Rust, there is no implicit this pointer.

Every fn item (function) of an impl block has to declare all its inputs, and if we want something like this, which Rust calls the "receiver", we also need to spell it out.

Cool bear's hot tip

Also, it's spelled self, not this.

If you don't have a receiver as the first parmeter, then it's not a "method", it's an "associated function", which you can call like that:

Rust code
let app = App { /* ... */ }; loop { // over here: App::update(); App::render(); if !app.running { break; } sleep(Duration::from_secs(1)); }

...but then it can't access any of the fields of app, the App instance we initialized just above.

So, if we want a thing like this, we need to add self explicitly:

Rust code
struct App { title: String, ticks_left: usize, running: bool, } impl App { fn update(self) { self.ticks_left -= 1; if self.ticks_left == 0 { self.running = false; } } fn render(self) { if self.ticks_left > 0 { println!("You turn the crank..."); } else { println!("Jack POPS OUT OF THE BOX"); } } }

There are now no errors left in that part of the code.

Since I've changed app.update() to App::update() to show off "associated functions", it is now complaining about my loop:

Shell session
$ cargo check --quiet error[E0061]: this function takes 1 argument but 0 arguments were supplied --> src/main.rs:13:9 | 13 | App::update(); | ^^^^^^^^^^^-- supplied 0 arguments | | | expected 1 argument ... 30 | fn update(self) { | --------------- defined here error[E0061]: this function takes 1 argument but 0 arguments were supplied --> src/main.rs:14:9 | 14 | App::render(); | ^^^^^^^^^^^-- supplied 0 arguments | | | expected 1 argument ... 34 | fn render(self) { | --------------- defined here

And as I see those diagnostics, it cements in my mind that the receiver - in that case, self - really is just a regular argument.

Which I can pass myself, if I want to:

Rust code
loop { App::update(app); App::render(app); if !app.running { break; } sleep(Duration::from_secs(1)); }

Or, I can just use the method call syntax, which is what I wanted to do in the first place:

Rust code
loop { app.update(); app.render(); if !app.running { break; } sleep(Duration::from_secs(1)); }

Now, according to my Java or C# experience, I might be thinking that this would be enough - that it would just work. But it doesn't.

Shell session
$ cargo check --quiet error[E0382]: use of moved value: `app` --> src/main.rs:14:9 | 4 | let app = App { | --- move occurs because `app` has type `App`, which does not implement the `Copy` trait ... 13 | app.update(); | --- value moved here 14 | app.render(); | ^^^ value used here after move (cut)

According to my C++ experience however, I'm feeling uneasy about the whole thing. Java and C# have "reference semantics" for classes (and we're really trying our best to make something that feels like a class), so similar code in those languages definitely ought to work.

But in C++, that's not the case. In C++, we know that if we just try to pass an instance of a class, it can either be "moved", or it can be "copied", depending on choices we make.

By looking up some more documentation on methods and receivers, I finally find out that this:

Rust code
impl App { fn update(self) { // ... } }

Is really a shorthand for this:

Rust code
impl App { fn update(self: App) { // ... } }

This once again reinforces the notion that self, although special in that it allows using "method call syntax", is really just a regular parameter.

And looking at the compiler's error again, I can see that having a parameter of type self: App will definitely do... the same as in C++: either it will move (as it does here), or it will be copied - which it would be, if our type "implemented the Copy trait", whatever that means.

Anyway, regardless of whether I'm using my C# or Java experience and doing a bit of catching up on reference semantics vs copy semantics, or if I use my C++ instinct to come to the same conclusion, I end up realizing that I don't want to get an App, I want to get a reference to an App.

Something like that:

Rust code
impl App { fn update(self: &App) { // ... } }

Except, as I'm just now learning, there is also a shorthand for that, and it's:

Rust code
impl App { fn update(&self) { // ... } }

So I use it, for both fn update and fn render.

Shell session
$ cargo check --quiet error[E0594]: cannot assign to `self.ticks_left` which is behind a `&` reference --> src/main.rs:31:9 | 30 | fn update(&self) { | ----- help: consider changing this to be a mutable reference: `&mut self` 31 | self.ticks_left -= 1; | ^^^^^^^^^^^^^^^^^^^^ `self` is a `&` reference, so the data it refers to cannot be written (cut)

The error count has steeply decreased - and we're back in "the Rust compiler is giving me good advice" territory, so I'm finally starting to get some confidence back, and just follow the directions:

Rust code
impl App { fn update(&mut self) { self.ticks_left -= 1; if self.ticks_left == 0 { self.running = false; } } fn render(&self) { if self.ticks_left > 0 { println!("You turn the crank..."); } else { println!("Jack POPS OUT OF THE BOX"); } } }

This makes sense to me, given my prior experience. Rust also has a concept of constness, it's just opt-out instead of opt-in. If this was C++, render would take a const reference, and update would take a regular reference.

It's just that, in Rust, "regular" means immutable.

Cool bear's hot tip

Immutable? Not const?

Immutable. "constness" is a complicated and error-prone concept (where does the keyword go? does it allow interior mutability?). Immutability is not.

Cool bear's hot tip

Are there other things that are immutable by default?

I don't know, I just started learning Rust, why are you asking me questions? Let's just focus on getting this thing compiled and running:

Shell session
$ cargo check --quiet error[E0596]: cannot borrow `app` as mutable, as it is not declared as mutable --> src/main.rs:13:9 | 4 | let app = App { | --- help: consider changing this to be mutable: `mut app` ... 13 | app.update(); | ^^^ cannot borrow as mutable
Cool bear's hot tip

Ah, see - there are other things that are immutable by default.

Yeah. There's "this" - the app, uh, variable.

Cool bear's hot tip

Okay let me look it up. The thing that is immutable there is... ah, I see.

Okay so it's not a variable.

It's not?

Cool bear's hot tip

No - it's a "binding".

I guess the name "variable" sort of implies that it can change (it can "vary"), whereas bindings are, well, immutable by default.

And what is a binding?

Cool bear's hot tip

From what I'm gathering, it's just a name you give to a value.

It says here that... it says here that a classic mistake is to consider the "variable" as a single thing, like "x is an integer", but that it's actually two things: there is an "integer value" somewhere (on the stack, on the heap, or wherever constants live), and there's the name you give to it - the binding.

And the binding is just the name, right?

Cool bear's hot tip

Well, that - and also, it can be mutable or immutable.

Okay, I think I get it. So when I do this:

Rust code
let app = App { /* ... */ };

There's a value of type App somewhere in memory. And since all memory is read-write, it can definitely be mutated (altered, modified, written to, changed, updated).

But then I bind it to a name, app - and because I don't explicitly say that it's a mutable binding, then that means I can never mutate it through app.

Cool bear's hot tip

I wanna say that sounds right, but also I have a dozen tabs open right now so, ask again later. Turns out there's a lot of things to be read about Rust.

Yeah, yeah, but... my app?

Cool bear's hot tip

Oh your app yeah, I don't know, didn't the compiler suggest a fix?

Oh right.

Rust code
fn main() { // new: `mut` keyword let mut app = App { title: "Jack in the box".to_string(), ticks_left: 4, running: true, }; // etc. }

I think... I think it should work now?

Wonderful!

And now... the framework

Now that I've figured out all of this, I don't want to have to repeat it every time I make another graphical app. I want to have some re-usable code.

And I don't mean "just copy paste it from my trusty USB key", I mean - have an actual framework, a "crate", as they say, maybe I'll even publish it so I can get started on a new project by just adding a line to my Cargo.toml.

So, because of my prior experience in C#, Java, C or C++, I follow along with my plans. We don't have classes, but we have traits - which, at first glance, feel a bit like Java interfaces.

So, what should the interface for "this project's logic" look like?

Let's look at App again:

Rust code
impl App { fn update(&mut self) { // ... } fn render(&self) { // ... } }

Okay, so we need an update method that needs to be able to mutate self, and a render method that does not need to mutate self, but still needs to read from it - because how is it going to render the app's state, if it can't read it?

We can definitely make a religion trait out of that:

Rust code
trait Client { fn update(&mut self); fn render(&self); }

And then we can separate the generic boilerplate (in App) from the project-specific logic (in MyClient):

Rust code
struct App { title: String, running: bool, } struct MyClient { ticks_left: usize, }

And then... then we have to implement the Client trait for the MyClient struct:

Rust code
impl Client for MyClient { fn update(&mut self) { self.ticks_left -= 1; if self.ticks_left == 0 { self.running = false; } } fn render(&self) { if self.ticks_left > 0 { println!("You turn the crank..."); } else { println!("Jack POPS OUT OF THE BOX"); } } }

But, hm, running is not a field of MyClient - it's a field of App.

So I guess Client also needs to be able to access the App.

Well, no biggie, I can just have it take a mutable reference to App - just like it takes a mutable reference to MyClient.

That change needs to be done both on the trait and on the impl block:

Rust code
trait Client { // new: `app: &mut App` fn update(&mut self, app: &mut App); fn render(&self); } impl Client for MyClient { // changed here, too: fn update(&mut self, app: &mut App) { self.ticks_left -= 1; if self.ticks_left == 0 { // and now we can change `running` app.running = false; } } fn render(&self) { // etc. } }

Finally, I can just remove update and render from the impl App block - in fact I think I'll just have a run method that has the loop and everything in it.

Rust code
impl App { fn run(&mut self) { println!("=== You are now playing {} ===", self.title); loop { self.client.update(self); self.client.render(); if !self.running { break; } sleep(Duration::from_secs(1)); } } }

Of course uhhh App doesn't have a client field yet. And in my new main method, nowhere do I get to specify that I want MyClient to be running:

Rust code
fn main() { let mut app = App { title: "Jack in the box".to_string(), running: true, }; app.run(); }

Let's see... the Client::update method looks like this:

Rust code
trait Client { fn update(&mut self, app: &mut App); }

Which is a shorthand for this:

Rust code
trait Client { fn update(self: &mut Self, app: &mut App); }

And Self in this context means Client (since we're in a trait Client block):

Rust code
trait Client { fn update(self: &mut Client, app: &mut App); }

So clearly we need a &mut Client.

Alright then.

Rust code
struct App { title: String, running: bool, client: &mut Client, }

And then in my main function, I can simply do this:

Rust code
fn main() { let mut client = MyClient { ticks_left: 4 }; let mut app = App { title: "Jack in the box".to_string(), running: true, client: &mut client, }; app.run(); }

This does not compile.

Oh no

The first thing the Rust compiler tells me is that I can't just say &mut Client.

Client is a trait - its concrete type could be anything! And at some point in the language's evolution, it was decided that to make that very clear, one should refer to it as dyn Client.

So:

Rust code
struct App { title: String, running: bool, // new: `dyn` client: &mut dyn Client, }

The second diagnostic is a lot more involved:

Shell session
$ cargo check --quiet error[E0106]: missing lifetime specifier --> src/main.rs:18:13 | 18 | client: &mut dyn Client, | ^ expected named lifetime parameter | help: consider introducing a named lifetime parameter | 15 | struct App<'a> { 16 | title: String, 17 | running: bool, 18 | client: &'a mut dyn Client, | error[E0228]: the lifetime bound for this object type cannot be deduced from context; please supply an explicit bound --> src/main.rs:18:18 | 18 | client: &mut dyn Client, | ^^^^^^^^^^

Now, since I've just started learning Rust, I have no idea what any of this means.

The lifetime... bound? Deduced?

Look pal, there's all the context you need:

Rust code
fn main() { let mut client = MyClient { ticks_left: 4 }; let mut app = App { title: "Jack in the box".to_string(), running: true, client: &mut client, }; app.run(); }

See? The client binding refers to a value that lives up until the end of main.

It definitely outlives app. Isn't that all the context you need to deduce the lifetime of App::client?

Also - App contains other fields, right? It has a bool, it even has a String, which is not just a primitive type (integers, etc.). And I didn't hear any complaints about those? Why should &mut dyn Client be any different?

Sure, sure, the compiler tells me how to solve it. It says if I just do this:

Rust code
struct App<'a> { title: String, running: bool, client: &'a mut dyn Client, }

...then everything is fine.

Well, almost:

Shell session
$ cargo check --quiet error[E0726]: implicit elided lifetime not allowed here --> src/main.rs:47:6 | 47 | impl App { | ^^^- help: indicate the anonymous lifetime: `<'_>`
Rust code
impl App<'_> { fn run(&mut self) { // etc. } }

Now everything is fine.

Wait, no:

Shell session
$ cargo check --quiet error[E0499]: cannot borrow `*self.client` as mutable more than once at a time --> src/main.rs:52:13 | 52 | self.client.update(self); | ^^^^^^^^^^^^------^----^ | | | | | | | first mutable borrow occurs here | | first borrow later used by call | second mutable borrow occurs here error[E0499]: cannot borrow `*self` as mutable more than once at a time --> src/main.rs:52:32 | 52 | self.client.update(self); | ----------- ------ ^^^^ second mutable borrow occurs here | | | | | first borrow later used by call | first mutable borrow occurs here

Everything is not fine.

Okay. OKAY. There's a lot going on there. Maybe I can't just take my prior experience in Java, C#, C or C++ and just.. just wing it.

Maybe Rust references are just really complicated.

How about pointers? C and C++ have pointers. C#... eh, it's complicated. Java doesn't have pointers, but it still has NullPointerException for some reason.

Can't we just use pointers? Can't we just make the borrow checker shut it for once?

Let's give it a try:

Rust code
// no lifetime parameter struct App { title: String, running: bool, // raw pointer client: *mut dyn Client, } struct MyClient { ticks_left: usize, } trait Client { // now takes a raw pointer to app fn update(&mut self, app: *mut App); fn render(&self); } impl Client for MyClient { fn update(&mut self, app: *mut App) { self.ticks_left -= 1; if self.ticks_left == 0 { // this is fine, probably unsafe { (*app).running = false; } } } fn render(&self) { if self.ticks_left > 0 { println!("You turn the crank..."); } else { println!("Jack POPS OUT OF THE BOX"); } } } impl App { fn run(&mut self) { println!("=== You are now playing {} ===", self.title); loop { // this converts a reference to a raw pointer let app = self as *mut _; // this converts a raw pointer to a reference let client = unsafe { self.client.as_mut().unwrap() }; // ..which we need because the receiver is a reference client.update(app); client.render(); if !self.running { break; } sleep(Duration::from_secs(1)); } } }

Theeeeeeere. Now it compiles. And it runs. It runs great! I can't wait to make other graphical applications using that framework. It has everything: command-line windows, an event loop... well, a loop.

Cool bear's hot tip

Yeah. But didn't you just throw all of Rust's safety guarantees out the window?

What.. just because I used a few unsafe blocks?

Cool bear's hot tip

Precisely yeah.

Well, if they wanted me to benefit from Rust's safety guarantees, maybe they shouldn't have made them so confusing. Maybe I shouldn't have to take a freaking college course just to understand why the compiler is making a certain suggestion.

Cool bear's hot tip

Okay, but-

No, no, I know, I'm being dramatic. But I'm still pretty frustrated that the compiler would tell me to "add a lifetime parameter" or something, when clearly my code can work perfectly fine without it!

Who needs references? Down! With! References! Down! With! Borrowck!

Cool bear's hot tip

...

Alright alright I'll cool off. I'm not saying Rust is a bad language though - I like some of the syntax. And I don't have to worry about header files... and there's a neat little package manager slash build system tool, it's neat. I might just keep using it. Just.. with pointers, I guess.

Cool bear's hot tip

Before you do that: I've just finished reading my dozen of browser tabs, and I think I can tell you a bit more about what's happening.

Would you be willing to try and let me explain? It won't be long.

It won't be long?

Cool bear's hot tip

Cool bear promise.

Alright then.

Enter school bear

Cool bear's hot tip

First, it's important to acknowledge that Rust is not like C#, Java, C or C++.

I don't know, it has pointers and references and everything. It even has curly braces.

Cool bear's hot tip

Yes. But no. It's different. It's fundamentally different. It allows you to write software that does the same thing, but you have to think about it differently - because Rust has different priorities.

And you have to learn how to think differently.

Okay, okay, fine, I'll learn. But nothing I can find online teaches me, you know, how to think differently.

Cool bear's hot tip

Well - it's very hard to teach. Because there's a lot of habits to unlearn. There's entirely new concepts. trait is not just a funny way to spell class.

Lifetimes are not just an "opt-in safety feature", it's not an afterthought, it sits right there, at the base of the whole language.

So you're saying I should not have been making a graphical app as my first project?

Cool bear's hot tip

Well... time will tell. It's good to pick projects that interest you, but even if you've done that kind of project before - lots of times - with another language, that doesn't mean it'll be necessarily easy for you to do in Rust.

Ugh, fine.

So you said Rust had "different priorities", what does that mean?

Cool bear's hot tip

Well, Rust cares a lot about "memory safety".

Yes, yes, so I keep reading.

Cool bear's hot tip

And for example, your program with pointers is not memory safe.

Not even a little.

But it runs fine. And I've looked at the code - several times, even - and I can't think of a single thing wrong with it.

Cool bear's hot tip

Sure! It runs fine now.

But what if you change something?

Why would I change something.

Cool bear's hot tip

Okay, what if someone else starts using your framework, and they reach that point:

Rust code
fn main() { let mut app = App { title: "Jack, now outside the box".to_string(), running: true, client: /* ??? */, }; app.run(); }

And they go: "client? what's a client?", and since they have prior experience with C#, Java, C, or C++, they go "ehh it probably doesn't matter, I can just pass null".

But there's no null keyword in Rust.

Cool bear's hot tip

Indeed, but there's std::ptr::null_mut().

Rust code
fn main() { let mut app = App { title: "Jack, now outside the box".to_string(), running: true, client: std::ptr::null_mut() as *mut MyClient, }; app.run(); }

Wait, where did they get MyClient from?

Cool bear's hot tip

Eh.. in this hypothetical situation, maybe your crate has an example client, and they've asked help from a friend to resolve the "type annotation needed" error and that's what they came up with.

Okay, fine, let's say they do that. Then what?

Cool bear's hot tip

Then it compiles fine!

And when they run it, they see this:

Shell session
$ cargo run --quiet === You are now playing Jack, now outside the box === thread 'main' panicked at 'called `Option::unwrap()` on a `None` value', src/main.rs:65:35 note: run with `RUST_BACKTRACE=1` environment variable to display a backtrace

Yeah, obviously it doesn't work - they're using it wrong.

And that's not a big deal - they can just, uh, run it with RUST_BACKTRACE=1 and see where the problem is.

Shell session
$ RUST_BACKTRACE=1 cargo run --quiet === You are now playing Jack, now outside the box === thread 'main' panicked at 'called `Option::unwrap()` on a `None` value', src/main.rs:65:35 stack backtrace: (cut) 13: core::panicking::panic at src/libcore/panicking.rs:56 14: core::option::Option<T>::unwrap at /home/amos/.rustup/toolchains/stable-x86_64-unknown-linux-gnu/lib/rustlib/src/rust/src/libcore/macros/mod.rs:10 15: some_app::App::run at src/main.rs:65 16: some_app::main at src/main.rs:10 (cut) note: Some details are omitted, run with `RUST_BACKTRACE=full` for a verbose backtrace.

And then they realize they have a problem, and they fix it, and everything is fine.

Cool bear's hot tip

Yes. That's actually not the worst scenario.

The worst scenario is if you hadn't used a helper such as as_mut(), and a null pointer was actually dereferenced in your code:

Rust code
impl App { fn run(&mut self) { println!("=== You are now playing {} ===", self.title); loop { let app = self as *mut _; // before: // let client = unsafe { self.client.as_mut().unwrap() }; // after: let client: &mut dyn Client = unsafe { std::mem::transmute(self.client) }; client.update(app); client.render(); if !self.running { break; } sleep(Duration::from_secs(1)); } } }

Eh, I don't really see the difference, let me try it:

Shell session
$ RUST_BACKTRACE=1 cargo run --quiet === You are now playing Jack, now outside the box === [1] 1082353 segmentation fault (core dumped) RUST_BACKTRACE=1 cargo run --quiet

Ah.

Cool bear's hot tip

Ah indeed. Now you have a segmentation fault.

Okay, yes, segmentation faults - I know these, thanks to my prior experience in C or C++. Those are fine too - you can just use a debugger to get a stacktrace, then make your way back to where that pointer actually became null, and in two shakes of a lamb's tail, you're on your way to shipping your app.

Cool bear's hot tip

Yes. Two or more shakes yes.

But that's not even the worst case scenario.

Bear, come on - always with the worst case scenarios. You worry too much!

Cool bear's hot tip

I do, but that's besides the point.

The worst case scenario is not a null pointer - it's a dangling pointer.

Rust code
fn main() { let client_ptr = { let mut client = MyClient { ticks_left: 4 }; &mut client as _ }; let mut app = App { title: "Jack, now outside the box".to_string(), running: true, client: client_ptr, }; app.run(); }

Hang on, what does this even do? Can you walk me through it?

Wait no, I think I see it - okay so you're declaring a new binding, client_ptr, and the right-hand-side expression is... a block?

Cool bear's hot tip

Yes, blocks are expressions in Rust.

Now that I like. Okay and the block makes a new MyClient, which it binds to client - a mutable binding. And then as, I think I've seen as before, is the casting operator, but you're casting it to _?

Cool bear's hot tip

Yes! I'm casting it to "figure it out, rustc".

Which, since I also use it in the App { ... } initializer below, and the left-hand-side is a &mut MyClient, rustc can figure out that it needs to be *mut MyClient.

Okay, that all makes sense.

And then... ohhhh I see it. I see it. Sneaky bear. Since the MyClient value is built in a block... which has its own scope... it'll be freed before App even has a chance to warm up, and the pointer will be dangling.

Cool bear's hot tip

Now you're getting it!

Okay but - sorry, I don't really see where the problem is still. This will also just result in a segmentation fault, which as I pointed out, anyone with C or C++ experience is more than familiar with.

I don't think your argument is as compelling as you think it is.

Cool bear's hot tip

Oh yes?

Why don't you try running it, then?

Sure.

Shell session
$ RUST_BACKTRACE=1 cargo run --quiet === You are now playing Jack, now outside the box === You turn the crank... You turn the crank... You turn the crank... Jack POPS OUT OF THE BOX

Wait. Why does it-

Cool bear's hot tip

Bwahahahahahaha. Yes, yes, it works.

Now run it in release mode.

Shell session
$ RUST_BACKTRACE=1 cargo run --quiet --release === You are now playing Jack, now outside the box === You turn the crank... You turn the crank... You turn the crank... You turn the crank...

Wait what

Shell session
You turn the crank... You turn the crank... You turn the crank... You turn the crank... You turn the crank...

Bear, help, why won't it stop

Shell session
You turn the crank... You turn the crank... You turn the crank... You turn the crank... You turn the crank...

WHY WON'T IT STOP TURNING THE CRANK

Cool bear's hot tip

Bwahahahahahahhahahahahahahah.

Shell session
You turn the crank... You turn the crank... You turn the crank... You turn the crank... You turn the crank...

THE CRANK TURNING MUST BE STOPPED, BEAR HELP

Cool bear's hot tip

Okay okay just Ctrl+C it.

AH. OKAY. IT STOPPED. WHAT THE HECK HAPPENED.

Cool bear's hot tip

Oh, nothing much. Just the worst case scenario: silent corruption.

YES IT WOULD APPEAR SO. I'M SORRY FOR SHOUTING I'M STILL SORT OF UNDER THE SHOCK OF THE ENDLESS CRANK TURNING

Cool bear's hot tip

That's okay. See the problem is... not all failures are noisy. Some failures are silent, and they're the worst kind.

Even worse, the program behaved differently in its debug build and its release build. Even if you had written tests, you wouldn't have caught it, because tests are built in debug by default.

WHEW YEAH THAT'S NOT GREAT

Cool bear's hot tip

It isn't. And you're just making a game involving Jack, and a box, and his unclear location relative to the box.

Imagine if you were instead making an operating system, or a web browser, or a device directly responsible for keeping someone alive.

YEAH THAT WOULD BE SUPER SCARY

Cool bear's hot tip

It would.

And those are not theoretical errors btw. Microsoft said in 2019 that 70% of its security bugs were memory safety issues.

And they're not the only ones saying it.

OKAY THEN - sorry - okay then, I guess it is a pretty important issue, and Rust helps with that?

Cool bear's hot tip

It does. It very much does.

Okay so how does Rust help with that?

Cool bear's hot tip

Well, let's start simple.

Here's a function that prints an i64 (signed 64-bit integer):

Rust code
fn show(x: i64) { println!("x = {}", x); }

It's a great little function! But it only works with i64.

Right. If we want to show something else, we either need to make other functions, or we need to make show generic.

Cool bear's hot tip

Exactly - like that:

Rust code
fn show<T>(x: T) { println!("x = {}", x); }

Yeah, generics. I know that because of my prior experience with C++, C#, or Java.

Cool bear's hot tip

Yeah! Except it doesn't work because we haven't said that T must be something that can be shown.

Something that.. can be shown? How do we say that?

Cool bear's hot tip

We add a constraint:

Rust code
use std::fmt::Display; fn show<T: Display>(x: T) { println!("x = {}", x); }

Or, the long-winded way:

Rust code
use std::fmt::Display; fn show<T>(x: T) where T: Display, { println!("x = {}", x); }

Ahhh and Display is... from what I can see in the standard library documentation, Display is a trait?

Cool bear's hot tip

That is correct. Display is a trait that represents a certain property of a type: the ability to be... displayed.

Okay, but what does that have to do with lifetimes?

Cool bear's hot tip

Well, what do you think is the difference between those two values:

Rust code
fn main() { { let one = MyClient { ticks_left: 4 }; } let two = MyClient { ticks_left: 4 }; // some other code println!("Thanks for playing!"); }

Well... one is in its own scope, so it'll get freed immediately after it's initialized. Whereas two will remain valid for the entire duration of main.

Cool bear's hot tip

That's exactly right! They have different lifetimes.

And can you tell me where the lifetimes are in that code?

Well they're... uh... no, I don't see them.

Cool bear's hot tip

Precisely.

You don't see them, because the compiler has deduced them. They exist, and they matter, and they determine what you can do with one and what you can do with two, but you cannot see them in the code, and you cannot name them.

I see.

Cool bear's hot tip

But because one and two have different lifetimes, they have different types.

Okay?

Cool bear's hot tip

So if you were to write a function like that:

Rust code
fn show_ticks(mc: &MyClient) { println!("{} ticks left", mc.ticks_left); }

...then you actually would've written a generic function.

What? That function isn't generic - where are the angle brackets? The chevrons? The <>?

Cool bear's hot tip

Oh, you can add them if you want:

Rust code
fn show_ticks<'wee>(mc: &'wee MyClient) { println!("{} ticks left", mc.ticks_left); }

Ah, so the other version was just a shorthand?

Cool bear's hot tip

Yes!

And the function is generic over the lifetime of reference?

Cool bear's hot tip

Technically, over the lifetime of the value the reference points to, but yes.

You could also make the function not generic, and ask for a specific lifetime, like 'static, which means the input should live forever.

Rust code
fn show_ticks(mc: &'static MyClient) { println!("{} ticks left", mc.ticks_left); }

Now show_ticks is not generic anymore.

And can we still use it? Like that?

Rust code
fn main() { let one = MyClient { ticks_left: 4 }; show_ticks(&one); }
Cool bear's hot tip

You can! But not like that:

Rust code
cargo check --quiet error[E0597]: `one` does not live long enough --> src/main.rs:5:16 | 5 | show_ticks(&one); | -----------^^^^- | | | | | borrowed value does not live long enough | argument requires that `one` is borrowed for `'static` 6 | } | - `one` dropped here while still borrowed

You can only use it if you have a value of type MyClient that lasts forever. And one dies when we reach the end of main, so that particular example doesn't work. And you don't know how to get a value of type MyClient that lasts forever.

Sure I do!

Rust code
static FOREVER_CLIENT: MyClient = MyClient { ticks_left: 4 }; fn main() { show_ticks(&FOREVER_CLIENT); }
Cool bear's hot tip

Oh, yeah, that works - that's actually why that lifetime is named 'static, static variables (in the data segment of an executable) live forever.

I was thinking more about something like this:

Rust code
fn main() { let client = MyClient { ticks_left: 4 }; let client_ref = Box::leak(Box::new(client)); show_ticks(client_ref); }

Oh, this, yeah, I don't know about this.

Cool bear's hot tip

Okay so - as soon as some of the arguments to a function are references, that function is generic.

Because the lifetime of "the value a reference points to" is part of the type of that reference. We just omit it a lot of the time, because mostly the compiler can figure it out.

Yeah I was about to ask - if we go back to the version the compiler suggested:

Rust code
struct App<'a> { title: String, running: bool, client: &'a mut dyn Client, } impl App<'_> { fn run(&mut self) { println!("=== You are now playing {} ===", self.title); loop { let app = self as *mut _; self.client.update(app); self.client.render(); if !self.running { break; } sleep(Duration::from_secs(1)); } } }

...then it requires explicit lifetime annotations. Even though - and I want to emphasize this - even though it knows exactly what the lifetime of app and client are here:

Rust code
fn main() { let mut client = MyClient { ticks_left: 4 }; let mut app = App { title: "Jack in the box".to_string(), running: true, client: &mut client, }; app.run(); }
Cool bear's hot tip

Yes! But consider this: lifetimes must be enforced throughout the entire program.

Not just in the scope of main.

Yeah. Still. The compiler knows how I use it. The compiler knows how I use it everywhere. Can't it just figure it out?

Cool bear's hot tip

Yes and no!

You use lifetime annotations (and bounds/constraints) for the same reason you use types. If add(x, y) only knows how to add i64 values, then that's the type of the argument it takes.

If a function needs to hold on to a value forever, then it takes a &'static T.

Okay, can you walk me through a bunch of examples?

Cool bear's hot tip

Sure!

Shell session
$ cargo new some-examples Created binary (application) `some-examples` package
Rust code
struct Logger {} static mut GLOBAL_LOGGER: Option<&'static Logger> = None; fn set_logger(logger: &'static Logger) { unsafe { GLOBAL_LOGGER = Some(logger); } } fn main() { let logger = Logger {}; set_logger(Box::leak(Box::new(logger))); }

Right, a logger. I'd see why you'd want to hold onto that forever.

Why do you use unsafe there?

Cool bear's hot tip

Mutable static variables are definitely dangerous.

Okay, next slide please?

Cool bear's hot tip
Rust code
use std::time::SystemTime; fn log_message(timestamp: SystemTime, message: &str) { println!("[{:?}] {}", timestamp, message); } fn main() { log_message(SystemTime::now(), "starting up..."); log_message(SystemTime::now(), "shutting down..."); }

Okay, no lifetime annotations this time - I'm guessing message is only valid for the entire duration of the log_message call.

And you don't need to hold on to it because... you just read from it, and then immediately stop using it. Okay.

What's a good example of needing to hold onto something for more than the duration of a function call, but less than the duration of a program?

Cool bear's hot tip

That's exactly where things get interesting!

Rust code
#[derive(Default)] struct Journal<'a> { messages: Vec<&'a str>, } impl Journal<'_> { fn log(&mut self, message: &str) { self.messages.push(message); } } fn main() { let mut journal: Journal = Default::default(); journal.log("Tis a bright morning"); journal.log("The wind is howling"); }

Wait, Default? #derive?

Cool bear's hot tip

sigh okay fine:

Rust code
struct Journal<'a> { messages: Vec<&'a str>, } impl Journal<'_> { fn new() -> Self { Journal { messages: Vec::new(), } } fn log(&mut self, message: &str) { self.messages.push(message); } } fn main() { let mut journal = Journal::new(); journal.log("Tis a bright morning"); journal.log("The wind is howling"); }

I see. The messages should live for as long as the journal itself.

Let me just try it out for myself... wait, it doesn't compile.

Cool bear's hot tip

No, it doesn't!

Rust code
$ cargo check -q error[E0312]: lifetime of reference outlives lifetime of borrowed content... --> src/main.rs:13:28 | 13 | self.messages.push(message); | ^^^^^^^ | note: ...the reference is valid for the lifetime `'_` as defined on the impl at 5:14... --> src/main.rs:5:14 | 5 | impl Journal<'_> { | ^^ note: ...but the borrowed content is only valid for the anonymous lifetime #2 defined on the method body at 12:5 --> src/main.rs:12:5 | 12 | / fn log(&mut self, message: &str) { 13 | | self.messages.push(message); 14 | | } | |_____^

So we haven't expressed the constraints correctly?

Cool bear's hot tip

No!

And it's easier to see if we don't use any shorthands:

Rust code
impl<'journal> Journal<'journal> { fn new() -> Self { Journal { messages: Vec::new(), } } fn log<'call>(&'call mut self, message: &'call str) { self.messages.push(message); } }

Wait you can do that?? Why do I keep seeing 'a everywhere?

Cool bear's hot tip

idk, it's short. When you only have one lifetime.. it's like x in maths.

Okay so, with that code, the error becomes:

Shell session
$ cargo check -q error[E0312]: lifetime of reference outlives lifetime of borrowed content... --> src/main.rs:13:28 | 13 | self.messages.push(message); | ^^^^^^^ | note: ...the reference is valid for the lifetime `'journal` as defined on the impl at 5:6... --> src/main.rs:5:6 | 5 | impl<'journal> Journal<'journal> { | ^^^^^^^^ note: ...but the borrowed content is only valid for the lifetime `'call` as defined on the method body at 12:12 --> src/main.rs:12:12 | 12 | fn log<'call>(&'call mut self, message: &'call str) { | ^^^^^

That is much better. So by using shorthands we accidentally borrowed it just for the duration of the call, when really we wanted to borrow it for the whole lifetime of our Journal.

Cool bear's hot tip

Yes, because we're literally storing it in the Journal.

So what we want is this:

Rust code
impl<'journal> Journal<'journal> { // omitted: `fn new` fn log<'call>(&'call mut self, message: &'journal str) { self.messages.push(message); } }

And if I wanted to bring back some shorthands... that:

Rust code
impl<'journal> Journal<'journal> { // cut fn log(&mut self, message: &'journal str) { self.messages.push(message); } }

And.. can I shorten it even more using '_?

Cool bear's hot tip

You cannot. Just like x as _ is casting to the "figure it out" type, '_ is the "idk, something" lifetime, and if you use it for both impl Journal<'_> and for &'_ str, it'll default to &'call, not to &'journal.

I see.

Okay so - you said Rust was looking over my shoulder, checking for lifetimes in the whole program, correct?

Cool bear's hot tip

That's right.

But a thought occurs - what if I wanted to return a Journal from some function?

Cool bear's hot tip

Well, it all depends.

If you try something like this:

Rust code
fn main() { let journal = get_journal(); } fn get_journal() -> Journal { let mut journal = Journal::new(); journal.log("Tis a bright morning"); journal.log("The wind is howling"); journal }

...you'll get a compile error.

Let me try it..

Shell session
$ cargo check --quiet error[E0106]: missing lifetime specifier --> src/main.rs:21:21 | 21 | fn get_journal() -> Journal { | ^^^^^^^ expected named lifetime parameter | = help: this function's return type contains a borrowed value, but there is no value for it to be borrowed from help: consider using the `'static` lifetime | 21 | fn get_journal() -> Journal<'static> { | ^^^^^^^^^^^^^^^^

Ah, yeah. So now I can't just write Journal, I have to write Journal<something>.

Okay, what if I write this:

Rust code
fn get_journal<'a>() -> Journal<'a> { let mut journal = Journal::new(); journal.log("Tis a bright morning"); journal.log("The wind is howling"); journal }

That seems to work! What's going on there?

Cool bear's hot tip

What's going on is that all your string constants: "Tis a bright morning", "The wind is howling", etc., all have the 'static lifetime.

So the resulting Journal has the 'static lifetime too?

Can I verify that somehow?

Cool bear's hot tip

Sure you can:

Rust code
fn main() { let journal: Journal<'static> = get_journal(); }

Oh right. It's just a type. I can just spell it out.

So what, how, uh... when would it prevent me from doing bad things?

Cool bear's hot tip

Here's one:

Rust code
fn get_journal<'a>() -> Journal<'a> { let s = String::from("Tis a dark night. It's also stormy."); let mut journal = Journal::new(); journal.log(&s); journal }

Let me check:

Shell session
$ cargo check --quiet error[E0515]: cannot return value referencing local variable `s` --> src/main.rs:26:5 | 25 | journal.log(&s); | -- `s` is borrowed here 26 | journal | ^^^^^^^ returns a value referencing data owned by the current function error: aborting due to previous error

Oh yeah. Oh that's good. I like that. I've seen warnings about that from my prior experience with C or C++, but not good like that.

Cool bear's hot tip

Yeah, and you know from your prior experience with Java or C# that in those languages it wouldn't even be a problem because of garbage collection.

Yeah, oh and, about that, is there a way for me to get roughly the same behavior as in C# or Java?

Cool bear's hot tip

There's always reference counting!

Rust code
use std::sync::Arc; #[derive(Default)] struct Journal { messages: Vec<Arc<String>>, } impl Journal { fn log(&mut self, message: Arc<String>) { self.messages.push(message); } } fn main() { let _journal: Journal = get_journal(); } fn get_journal() -> Journal { let s = Arc::new(String::from("Tis a dark night. It's also stormy.")); let mut journal: Journal = Default::default(); journal.log(s); journal }

Wait, wait, hold on - where did all the lifetimes go?

Cool bear's hot tip

We don't need them anymore! Because with smart pointers like Arc and Rc, as long as we hold at least one reference (one Arc<T>) to a value, the value lives on.

But wait, &T and Arc<T> seem like completely different syntaxes... but they're both actually pointers?

Cool bear's hot tip

They're both pointers.

What if I have an Arc<T> and I need a &T? Am I stuck writing methods that take an Arc<T>?

Cool bear's hot tip

No, you're not stuck at all.

You can definitely borrow (get a reference to) the contents of an Arc<T> - it'll be valid for as long as the Arc<T> lives.

Any examples?

Cool bear's hot tip

Sure:

Rust code
use std::sync::Arc; struct Event { message: String, } #[derive(Default)] struct Journal { events: Vec<Arc<Event>>, } impl Journal { fn log(&mut self, message: String) { self.events.push(Arc::new(Event { message })); } fn last_event(&self) -> Option<&Event> { self.events.last().map(|ev| ev.as_ref()) } } fn main() { // muffin }

Ah, you're talking about last_event, right? Even though we're holding onto values of type Arc<Event>, we can turn those into an &Event.

Cool bear's hot tip

Yes, and more often than not, the coercion is automatic.

So, for example, this works:

Rust code
impl Event { fn print(&self) { println!("Event(message={})", self.message); } } fn main() { let ev = Arc::new(Event { message: String::from("well well well."), }); ev.print(); }

Convenient! I guess last_event could just as well return an Arc<Event>, right? Since it's actually a pointer, shouldn't we be able to just... increase the reference count by one and return that?

Cool bear's hot tip

Yes, by cloning it! And by "it" I mean the smart pointer, not the Event:

Rust code
impl Journal { fn last_event(&self) -> Option<Arc<Event>> { self.events.last().map(|x| Arc::clone(x)) } }

Note that we're using Option here, because if the journal is empty, there will be no last event.

Also, Option has a nice shorthand for what we're doing:

Rust code
impl Journal { fn last_event(&self) -> Option<Arc<Event>> { self.events.last().cloned() } }

But wait! Wait a minute. Does that mean I can get rid of the lifetime problem in my application by just using Arc<T>?

Cool bear's hot tip

You absolutely 100% can!

But do you need to?

What do you mean, do I need to?

Cool bear's hot tip

What I mean is: do you need shared ownership? Will you have other references to the Client?

Uhh I don't know yet. Maybe I don't, maybe it's okay if App has sole ownership of Client?

Cool bear's hot tip

In that case, you want a Box<T>.

What's that? Another smart pointer?

Cool bear's hot tip

Yes! It's exactly the same size as a reference.

Rust code
struct Foobar {} fn main() { let f = Foobar {}; let f_ref = &f; let f_box = Box::new(Foobar {}); println!("size of &T = {}", std::mem::size_of_val(&f_ref)); println!("size of Box<T> = {}", std::mem::size_of_val(&f_box)); }
Shell session
$ cargo run --quiet size of &T = 8 size of Box<T> = 8

Right, eight bytes, because we're on 64-bit.

Okay so I can just use a Box then? Let me try that...

Rust code
// back in `some-app/src/main.rs` fn main() { let client = MyClient { ticks_left: 4 }; let mut app = App { title: "Jack in the box".to_string(), running: true, client: Box::new(client), }; app.run(); } struct App { title: String, running: bool, client: Box<dyn Client>, } impl App { // unchanged }

Okay, this does work... what's the difference with the version with the lifetime annotations and such?

Cool bear's hot tip

As far as generated code goes - there is no difference. It should be the exact same binary (in release mode at least).

But in terms of ownership: in the version with lifetime annotations, App borrowed Client from fn main.

Now, App owns Client - and so Client lives for as long as the App.

Okay. Well, this is a lot of new information but okay. But it works the same right?

Cool bear's hot tip

The exact same, yes!

And I didn't have to change any of the other code, like that over here:

Rust code
impl App { fn run(&mut self) { println!("=== You are now playing {} ===", self.title); loop { let app = self as *mut _; self.client.update(app); self.client.render(); if !self.running { break; } sleep(Duration::from_secs(1)); } } }

...even though Client::update takes a &mut self and self.client is now a Box<dyn Client>, because it does that same magic that coerces a smart pointer into a &T or &mut T as needed?

Cool bear's hot tip

Yes, autoderef!

It's not actual magic, by the way. It's just two traits: Deref and DerefMut.

Ohh. It's traits all the way down.

Cool bear's hot tip

And then some.

Okay, things are clearer now. But... we still have a *mut App in our code, right here:

Rust code
let app = self as *mut _;

...which we pass to Client::update:

Rust code
trait Client { // hhhhhhhhhhhhhhhhhhhhhhhere. fn update(&mut self, app: *mut App); fn render(&self); }

Can we get rid of that now?

Cool bear's hot tip

I don't know, try it, see how the compiler likes it.

Oh okay.

Rust code
trait Client { fn update(&mut self, app: &mut App); fn render(&self); } impl Client for MyClient { fn update(&mut self, app: &mut App) { self.ticks_left -= 1; if self.ticks_left == 0 { app.running = false; } } fn render(&self) { // omitted } } impl App { fn run(&mut self) { println!("=== You are now playing {} ===", self.title); loop { // remember `&mut self` is just `self: &mut Self`, // so `self` is just a binding of type `&mut App`, // which is the exact type that `Client::update` takes. self.client.update(self); self.client.render(); if !self.running { break; } sleep(Duration::from_secs(1)); } } }

Now we just compile it, and...

Shell session
$ cargo check --quiet error[E0499]: cannot borrow `*self.client` as mutable more than once at a time --> src/main.rs:52:13 | 52 | self.client.update(self); | ^^^^^^^^^^^^------^----^ | | | | | | | first mutable borrow occurs here | | first borrow later used by call | second mutable borrow occurs here error[E0499]: cannot borrow `*self` as mutable more than once at a time --> src/main.rs:52:32 | 52 | self.client.update(self); | ----------- ------ ^^^^ second mutable borrow occurs here | | | | | first borrow later used by call | first mutable borrow occurs here

Arghh. No, see? Same error.

Cool bear's hot tip

Ah, that's when the "learn to think the Rust way" comes in.

Yes but WHAT DOES IT MEAN, ahhhhhhh.

Cool bear's hot tip

Okay, remain calm - currently App holds everything right?

The title, the running boolean flag, and the client.

Yes, that's correct.

Cool bear's hot tip

And the problem is that, since Client::update takes a &mut Client, just by doing self.client.update(...), we're borrowing self mutably once.

Uh-huh.

Cool bear's hot tip

And then Client::update also wants a &mut App, so we need to borrow ourselves mutably a second time.

Yeah.

Cool bear's hot tip

And that's forbidden.

Yeah seems like a big no-no for some reason.

Cool bear's hot tip

So here's an idea - how about we separate the state a bit.

On one side, we'll have.. well, the client. And on the other side, we'll have, well, the state of the app.

What do you mean, "separate" - like, make a new struct?

Cool bear's hot tip

Exactly! Name it AppState or something.

Okay, let's try it.

Rust code
struct App { client: Box<dyn Client>, state: AppState, } struct AppState { title: String, running: bool, }

Okay bear, I'm done, what now?

Cool bear's hot tip

Well now we carefully review the MyClient::update method.

Let me pull it up from the mainframe.. here it is:

Rust code
impl Client for MyClient { fn update(&mut self, app: &mut App) { self.ticks_left -= 1; if self.ticks_left == 0 { app.running = false; } } }
Cool bear's hot tip

Okay - think carefully.

Does MyClient::update need to have access to the whole App?

Well... the only thing it's doing is... toggling the running flag.

Cool bear's hot tip

Which is now where?

Oh, it's in AppState.

Cool bear's hot tip

So MyClient really only needs..

..a mutable reference to AppState! Say no more.

Rust code
trait Client { fn update(&mut self, state: &mut AppState); fn render(&self); } impl Client for MyClient { fn update(&mut self, state: &mut AppState) { self.ticks_left -= 1; if self.ticks_left == 0 { state.running = false; } } fn render(&self) { // omitted } }
Cool bear's hot tip

There!

And then you need to update a few other things in the application...

I'm getting to it! First the main function:

Rust code
fn main() { let client = MyClient { ticks_left: 4 }; let mut app = App { state: AppState { title: "Jack in the box".to_string(), running: true, }, client: Box::new(client), }; app.run(); }

..and then App::run:

Rust code
impl App { fn run(&mut self) { println!("=== You are now playing {} ===", self.state.title); loop { self.client.update(&mut self.state); self.client.render(); if !self.state.running { break; } sleep(Duration::from_secs(1)); } } }

...and then:

Shell session
$ cargo check Finished dev [unoptimized + debuginfo] target(s) in 0.00s

...then it works! Did we do it?

Cool bear's hot tip

No, you did it.

Whoa. I feel smarter already. Okay but, uh, aren't we still mutably borrowing self twice?

Cool bear's hot tip

No, you're, uh... no, you're borrowing different parts of self mutably. That's perfectly okay.

So splitting state is good? Because it makes it easier to track the lifetimes of various things?

Cool bear's hot tip

Very good. Much recommend.

In fact, it's good even outside of Rust. If you ever go back to C, or C++, after a long period of doing Rust, you may find yourself writing things a little differently.

Phew, okay.

Any last words? I mean, do you have some more advice for my graphical app?

Cool bear's hot tip

As a matter of fact I do!

One pattern that is especially useful in a language like Rust, that doesn't let you write code that "works right now" but would definitely break if you changed it in a way that silently breaks the invariants only you, the code designer, have in your mind, is the following.

Instead of directly performing mutation, consider returning values that describe the mutation that should be performed.

Oh yeah, I g... I'm sorry bear I don't, I don't actually get it, can you show me an example?

Cool bear's hot tip

Sure! In your graphical app - you're passing the whole of &mut AppState... just to be able to quit the application, right?

Ah yeah, pretty much yep.

Cool bear's hot tip

And it's the oooonly thing you'll ever do with that AppState. You'll never, for example, set running to true. You'll never mess with any other part of the AppState.

No yeah that's fair.

Cool bear's hot tip

So you could just as well have it return a boolean: true if it should keep running, and false if it should quit.

Ah yeah, let me try that:

Rust code
trait Client { // returns false if the app should exit fn update(&mut self) -> bool; fn render(&self); } impl Client for MyClient { fn update(&mut self) -> bool { self.ticks_left -= 1; self.ticks_left > 0 } fn render(&self) { if self.ticks_left > 0 { println!("You turn the crank..."); } else { println!("Jack POPS OUT OF THE BOX"); } } } struct App { client: Box<dyn Client>, state: AppState, } struct AppState { title: String, } impl App { fn run(&mut self) { println!("=== You are now playing {} ===", self.state.title); loop { let running = self.client.update(); self.client.render(); if !running { break; } sleep(Duration::from_secs(1)); } } }

Ohhhh yeah. I like that. Fewer references! Fewer "opportunities" to fight with the borrow checker. I could get used to that.

Cool bear's hot tip

Right? Although we've lost in clarity a little bit - it's not immediately obvious why update() returns a bool. I can tell you know that this is the case, because you added a comment to indicate what it actually does.

Yeah, guilty. I did add a comment because of that.

Cool bear's hot tip

But you didn't add a comment everywhere did you? It's not in the impl Client for MyClient block, and it's not in App::run where update is called.

That's true, I suppose you would have to do some digging to find what it does.

Cool bear's hot tip

What if I told you it didn't have to be like this?

It doesn't?

Cool bear's hot tip

No!

Rust has enums, and you should use them.

You could have an enum like that:

Rust code
enum UpdateResult { None, QuitApplication, }

Oh.. and that's what update would return?

Let's try it:

Rust code
trait Client { fn update(&mut self) -> UpdateResult; fn render(&self); } impl Client for MyClient { fn update(&mut self) -> UpdateResult { self.ticks_left -= 1; if self.ticks_left == 0 { UpdateResult::QuitApplication } else { UpdateResult::None } } } impl App { fn run(&mut self) { println!("=== You are now playing {} ===", self.state.title); loop { let res = self.client.update(); self.client.render(); match res { UpdateResult::None => {} UpdateResult::QuitApplication => { return; } } sleep(Duration::from_secs(1)); } } }

Oh that's neat. Very neat. You're so wise bear.

Thanks for all the help.

Cool bear's hot tip

No worries!

This article was made possible thanks to my patrons: Alex Rudy, Romet Tagobert, Douglas Creager, Gus W, Corey Alexander, Molly Howell, knutwalker, Zachary Dremann, Sebastian Ziebell, Amber Kowalski, T, Juniper Wilde, Paul Kline, Kristoffer Ström, Astrid Bek, Yoh Deadfall, Justin Ossevoort, taziden, Harsh Shandilya, Tomáš Duda, Jeremy Banks, Rasmus Larsen, Torben Clasen, Sam Rose, C J Silverio, Walther, Pete Bevin, Shane Sveller, Thomas Schultz, Ivan Dubrov, jer, Wonwoo Choi, João Veiga, Adam Perry, Benjamin Röjder Delnavaz, Justin Liew, Matt Jadczak, tavr, Mara Bos, Jonathan Knapp, Mayfield Reynolds, Maximilian, Seth Stadick, brianloveswords, Sean Bryant, Sebastian Zimmer, Fernando, Makoto Nakashima, Geert Depuydt, Geoff Cant, Geoffroy Couprie, Michael Alyn Miller, o0Ignition0o, Zaki, Raphael Gaschignard, Romain Ruetschi, Ignacio Vergara, Pascal, Jane Lusby, Nicolas Goy, Ted Mielczarek, Someone, Jérémy Gtld, Ryszard Sommefeldt, Jesús Higueras, Aurora.

If you liked this article, please support my work on Patreon!

Become a Patron

Looking for the homepage?
Another article: Recursive iterators in Rust