Stuff the Identity Function Does (in Rust)
The identity function looks like this in Rust:
/// The identity function.
fn id<T>(x: T) -> T { x }
id
returns the same value that is passed in:
assert_eq!(1, id(1));
Beyond the obvious, it does some curious and fun things!
You can test this blog post’s code in the Rust Playground.
id Type Hints or Coerces
let string = "hi".to_string();
// Coerce a &String to &str, with id:
match id::<&str>(&string) {
"hi" => {}
_ => panic!("at the disco"),
}
No magic, it’s just that you can specify with an explicit type which identity function you are calling. If the expression can coerce to that type, then it compiles.
id Forces References To Move
Let’s say we have a simple recursive datastructure:
struct List {
next: Option<Box<List>>,
}
And we want to walk it, with a mutable reference, through a loop.
impl List {
fn walk_the_list(&mut self) {
let mut current = self;
loop {
match current.next {
None => return,
Some(ref mut inner) => current = inner,
}
}
}
}
Looks good? Rustc disagrees! (compile in the playground)
error: cannot borrow `current.next.0` as mutable more than once at a time [E0499] Some(ref mut inner) => current = inner, ^~~~~~~~~~~~~
It turns out Rust’s mutable references do something interesting, and most of
the time it’s very useful: when they are passed, they reborrow the local variable
rather than move it. The explicit equivalent of the reborrow would be &mut *current
.
id
tells a mutable reference to move instead of reborrow! This way it compiles:
impl List {
fn walk_the_list(&mut self) {
let mut current = self;
loop {
match id(current).next {
None => return,
Some(ref mut inner) => current = inner,
}
}
}
}
This is a point where Rust could improve by learning to infer whether to
reborrow or move mutable references. Until then, we have id
.
id Makes Immutable Locals Mutable
id
returns just the same thing as you pass in. Except it’s now an rvalue, and
implicitly mutable.
impl List {
fn consume_the_list(self) {
// error: cannot borrow immutable local variable `self` as mutable
// self.walk_the_list();
id(self).walk_the_list();
}
}
This is no violation of Rust philosophy. Using mut
on locals is simple and
pragmatic, and mutability radiates from the owner. If your value is now
a temporary, it’s not owned by an immutable binding anymore (or any other
variable binding).
Rust has Dedicated Syntax for This!
If you thought that was cryptic, here’s one better. The secret syntax is just
{
and its companion }
, and it allows you to manipulate move semantics just
the same way id
does:
impl List {
fn walk_the_list_with_braces(&mut self) {
let mut current = self;
loop {
match {current}.next {
None => return,
Some(ref mut inner) => current = inner,
}
}
}
fn consume_the_list_with_braces(self) {
{self}.walk_the_list_with_braces();
}
}
Force a Move Today
If you actually use this, I think moving
is actually a pretty good name
(move
is taken, it’s a keyword). Save the {}
for obfuscation contests.
/// The identity function.
///
/// Also forces the argument to move.
fn moving<T>(x: T) -> T { x }
Epilogue
It’s February 2017 and this is hilarious:
Calling id::<&T>(p)
, since it’s a passing a shared reference, inserts
a noalias
annotation for the pointer p
, which might otherwise not have been
there! As soon as llvm’s metadata propagation improves, it might even have
actual use.