14Feb

Engineering Notebook: More Learning Rust

Posted by Elf Sternberg as Uncategorized

mem::replace()

In the Learning Rust With Too Many Linked Lists, there’s a bit of code that looks like this to build a new element and append it to the head of the list:

let new_node = Box::new(Node {
       elem: elem,
       next: mem::replace(&mut self.head, Link::Empty),
});

self.head = Link::More(new_node);

The problem here is that if we just had next: self.head, what happens is that next accepts self.head as a move, not a pointer! self.head would be invalid, and Rust doesn’t like that. So using mem::replace() puts a value into self.head that’s valid, and then on the next line we construct and move the Link(node) into self.head.

mem::replace() returns the value to be moved (here, self.head), so you can continue to work with the old value in your new variable (and lifetime) while keeping the old variable available.

Rust is the only language I know of where:

f(x);
f(x);

… results in an error message. But because the first call moves x into f, and it is no longer invalid to use in the parent scope. I’m starting to really wrap my head around this, but it’s not easy at all.

take()

In Rust, the Option<> is a “it might be here, or it might not be. We’ll track that for you.” This “If it’s here, give it to me and make the original temporarily None so I can work with it,” is so common for Option<> that there’s a method, Option.take(), that does exactly the above, but much cleaner.

The method above becomes:

let new_node = Box::new(Node {
   elem: elem,
   next: self.head.take(),
});

self.head = Some(new_node);

Awesome.

as_ref()

In the linked lists, the head function returns the head value, if any. The short form of this is:

pub fn peek(&self) -> Option<&T> {
        self.head.as_ref().map(|node| { &node.elem })
}

The thing about peek() is that we don’t want to actually get the element, that is, we don’t want to move it, we want to borrow it. So we need tell rust that we don’t want to own the head node, just borrow it. But the head object isn’t a reference, it’s a value. .as_ref() turns the value into a reference-to-value: this tells the compiler we don’t want to own the value, we want to borrow it. It turns a compiler-based own into a borrow. That’s all it does.

Rc::try_unwrap()

In the “Linked lists with many possible heads” example, the Drop is interesting because it can get expensive: the reference counters need to be incremented during the check for dropping, then decremented immediately afterward since, no really, we want to drop the element and its cell.

The tutorial says that there’s a function, Rc::try_unwrap(), that exploits an interesting feature of Rust. Start with this:

list1 -> A --------v
list2 ------> B -> C -> D

If you drop list2, you want it to stop after only dropping B. You don’t want it proceeding, because list1 has two objects pointing at C. Rc::try_unwrap() will perform a move if and only if the reference count of an object is one, that is, no other object or scope cares about the object. By repeatedly using Rc::try_unwrap() on list items, the Drop function can be halted when the try_unwrap fails, because move semantics can’t be applied anymore.

Yes, D has only one reference. That’s okay, but the Drop function halts upon hitting C, because try_unwrap fails and you can break when it does; all the correct items are dropped, and everything else remains.

The tutorial says that Rc::try_unwrap() is only available in nightly. I’m using rustc Stable 1.23.0 and it works just fine.

Comment Form

Subscribe to Feed

Categories

Calendar

February 2018
M T W T F S S
« Jan   Mar »
 1234
567891011
12131415161718
19202122232425
262728