-
-
Save Aatch/5734372 to your computer and use it in GitHub Desktop.
An example and explanation of how to use lifetimes and borrowing to avoid copying, while maintaining safety.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
extern mod extra; | |
use extra::json::*; | |
/* | |
* This function manages to do absolutely no copying, which is pretty cool. | |
* | |
* "What are all those `'r`s?" you ask. Well, they're liftime parameters. They | |
* indicate how long something lasts (before it's freed). They can't change how | |
* long something lives for, they only allow you to tell the compiler stuff it | |
* would otherwise not be able to figure out. | |
* | |
* In this case, we have `'r`, but the name doesn't matter, just the initial | |
* single quote that makes it different to a type parameter. We use this lifetime | |
* in two places: the type signature of `json`, `&'r Json` which should be read | |
* as "A borrowed pointer to a Json type, that lasts for at least `'r`". It is also | |
* in the return type, `Option<&'r str>`. Ignoring the wrapping Option type, this | |
* should be read as "a slice into a string that lasts for at least `'r`". | |
* | |
* More comments inline! | |
*/ | |
fn get_string<'r>(json: &'r Json, key: &str) -> Option<&'r str> { | |
// Dereferencing the json variable here doesn't matter, it just allows us | |
// to omit the leading `&` in the match arms. | |
match *json { | |
// This is a matching pattern against the `Object` variant of the `Json` | |
// enum. Since this variant contains a map, and that's what we want, we | |
// bind the variable `map` to it. The `ref` in there says that we want | |
// to bind to `map` by _reference_. | |
Object(ref map) => { | |
// Here, we use `find_equiv`, instead of `find`. `find_equiv` works | |
// for types that are **equivalent** to the key of the map. | |
// We use this method because otherwise we would need to use a ~str, | |
// which would be moved into this function, forcing the caller to | |
// either copy the string if they wanted to keep using it, or allocate | |
// unnecessarily if they wanted to use a static string (like a string | |
// literal). | |
// | |
// NB: Currently a limitation means we have to pass the slice by | |
// reference. | |
match map.find_equiv(&key) { | |
// Here we match against the `Some` variant of `Option`, and | |
// the `String` variant of `Json`. The `&` in there isn't borrowing, | |
// instead it's matching a borrowed value. | |
// Then, inside the the match, we bind the actual `~str` by reference | |
// to `s`. We can't bind it directly because that would mean we would | |
// move it out of the match, which we aren't allowed to do. | |
Some(&String(ref s)) => { | |
// Finally, construct a new `Some`, and turn it into a slice. The | |
// slice lasts as long as the string being sliced. | |
// This returns and the caller knows that the enclosed string (if | |
// there is one) lasts for at least as long as the json object | |
// it came from. | |
// | |
// (We could not slice and return a `&'r ~str` here, but that would | |
// behave oddly and be hard to use in other places, so this is much | |
// easier) | |
Some(s.as_slice()) | |
} | |
_ => None | |
} | |
}, | |
_ => None | |
} | |
} | |
/* | |
* This works because the lifetime given in the function signature is threaded down | |
* through the code. Each ref and match knows the lifetime and passes it on. Furthermore, | |
* the method calls know about lifetimes, so `find_equiv` gives you a pointer that lasts | |
* for as long as the map it came from and `as_slice` gives you a slice that lasts for as | |
* long as the string it came from. The compiler matches up all these lifetimes, and checks | |
* to see if what you say is true. | |
* | |
* The advantage of this version is that it moves the choice of memory usage to the caller. | |
* If they want to copy the string, then they can, but what if they just want to check to see | |
* if the key is in the object? Previously that would have incurred an allocation of the string, | |
* which could be huge. Much better idea to instead give them a slice and let them copy it if | |
* they want, instead of forcing it on them. | |
*/ | |
fn main() { | |
// Use the magic stringify! macro because json tokenizes! | |
let json = from_str(stringify!( | |
{ | |
"language": "Rust", | |
"level" : 9001 | |
} | |
)).unwrap(); | |
println(fmt!("%?", get_string(&json, "language"))); // Prints Some("Rust") | |
println(fmt!("%?", get_string(&json, "level"))); // Prints None | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Hey, this is a great example.
I've a rust-for-real repo with some examples / tutorials about rust. Would you like to propose a PR with this great example?
TBH, the
rust-for-real
is moving slow, but I plan to keep adding things