gdritter repos documents / master posts / some-rust-errors.md
master

Tree @master (Download .tar.gz)

some-rust-errors.md @master

30d60df
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
I was an early evangelist of the [Rust language] at my office, having
followed it development for a few years, but I still haven't written
any large programs in it. So I don't yet have a really strong opinion
on the language in practice.

However, [a coworker] has started writing a program in Rust, and it
has given me the opportunity to better understand the language so I
can answer his questions. Whenever something would go wrong, he would
sent me the code, or a representative snippet, and demand that I
explain why it wasn't working. Some of these questions were actually
quite difficult, and in at least one case, I was briefly convinced
that I had found a compiler bug.

Because these are some tricky interactions with the language, I wanted
to write this up as a second-hand experience report, describing the
problems that came up and explaining why they were problems.[^1]

[^1]: Some of these I suspect will be alleviated by better error
messages, but are probably niche enough that improving these errors
hasn't been a high priority.

# Problem One: Determining Temporary Lifetimes

Rust has a simple rule for determining the lifetimes of temporary values.
A temporary value is any value which is not directly bound to a name, but
is created somewhere in your program. For example, the return value of
a function that is not directly assigned is a temporary, or a struct
created for the express purpose of taking a reference to it.

Rust's rule is that, generally, temporaries live only for the statement
in which they are created. For illustration's sake, let's create an
empty struct with a noisy constructor and destructor, so we see when
values are being created or destroyed:

~~~
struct Thing;

impl Thing {
    fn new() -> Thing {
        println!("Created Thing");
        Thing
    }
}

impl Drop for Thing {
    fn drop(&mut self) {
        println!("Destroyed Thing");
    }
}
~~~

If I create something and don't bind it to a name, then it'll get destroyed
before the next line executes:

~~~
fn main() {
    Thing::new();
    println!("fin.");
}
/* This prints:
> Created Thing
> Destroyed Thing
> fin.
*/
~~~

The exception to this rule happens if I bind _a reference to the thing_.
The thing itself is still a temporary because, even though we can access
it, there's nothing in scope that has ownership over it, we can't pass
the ownership elsewhere or force it to drop or anything. However, if we
have a reference to it (or some part of it), then it will live as long
as the reference does. For example, because of the reference here, this
temporary will live longer than before:

~~~
fn main() {
    let r = &Thing::new();
    println!("fin.");
}
/* This prints:
> Created Thing
> fin.
> Destroyed Thing
*/
~~~

This heuristic _only fires of you're directly binding the temporary to
a reference in that expression_. This came up for my coworker because
he had some initialization logic that he thought would be better served
by pushing it into a function, so he wrote the equivalent of

~~~
fn mk_thing_ref(thing: &Thing) -> &Thing {
    thing
}

fn main() {
    let r = mk_thing_ref(&Thing::new());
    println!("fin.");
}
~~~

This refactor means that the temporary returned by `Thing::new()` is no
longer directly being bound to a reference, and therefore the rule
no longer applies: the result of `Thing::new()` will die before the
next line. This is a problem, because `r` continues to exist after that
line, which means this program is rejected by the Rust compiler.

~~~
temp.rs:21:21: 21:33 error: borrowed value does not live long enough
temp.rs:21     let r = mk_ref(&Thing::new());
                               ^~~~~~~~~~~~
temp.rs:21:35: 23:2 note: reference must be valid for the block suffix following statement 0 at 21:34...
temp.rs:21     let r = mk_ref(&Thing::new());
temp.rs:22     println!("fin.");
temp.rs:23 }
temp.rs:21:5: 21:35 note: ...but borrowed value is only valid for the statement at 21:4
temp.rs:21     let r = mk_ref(&Thing::new());
               ^~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
temp.rs:21:5: 21:35 help: consider using a `let` binding to increase its lifetime
temp.rs:21     let r = mk_ref(&Thing::new());
               ^~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
error: aborting due to previous error
~~~

In this case, the error is clearer, but in my coworker's case, he
was trying to encapsulate much more elaborate initialization logic
that abstracted away gritty details, and was confused that what should
be an equivalent refactor no longer worked. It didn't help that he
was initializing something with closures, making him believe that it
was the closures that were at fault.

# Problem Two: Lifetimes of Trait Objects

A different lifetime problem came up elsewhere: