Created
February 22, 2021 18:17
-
-
Save benkay86/c60b46320a0506c43afa99517d3bd7c6 to your computer and use it in GitHub Desktop.
Receiving a borrowed type as either borrowed or owned in Rust
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
// In Rust, if you are going to access some data without mutating it you | |
// typically express this with a borrow `&`, for example: | |
// | |
// ``` | |
// fn readonly_access(s: &str) { | |
// println!("{}", s); | |
// } | |
// ``` | |
// | |
// Sometimes when designing an API you have decided not to mutate some data but | |
// would like to have the option of borrowing *or* taking ownership, but you | |
// don't want to have dupliate code like this: | |
// | |
// ``` | |
// fn borrowed(s: &str) { | |
// println!("{}", s); | |
// } | |
// fn owned(s: String) { | |
// println!("{}", s); | |
// } | |
// ``` | |
// | |
// The following code demonstrates how to write a generic function that treats | |
// some generic type as borrowed but receives it as either borrowed or owned. | |
// For this example, the received type is generic for the `Display` trait and | |
// is "spoken" by the character Kuiil from Disney's Mandalorian. | |
// Case 1: Free function receiving type. | |
// If T is a reference (e.g. &str) then this monomorphizes/desugars to: | |
// | |
// ``` | |
// fn speak(msg: &str) { | |
// println("Says, {}", msg); | |
// } | |
// ``` | |
// | |
// If T is a value (e.g. String) then this monomorphizes/desugars to: | |
// | |
// ``` | |
// fn speak(msg: String) { | |
// // `msg` moved onto function stack | |
// let msg = &msg; | |
// // original `msg` shadowed by reference to stack | |
// println!("Says, {}", msg); | |
// // value of `msg` is dropped when stack unwinds | |
// } | |
// ``` | |
fn speak<T: std::fmt::Display>(ref msg: T) { | |
println!("{}\nI have spoken.", msg); | |
} | |
// Case 2: Trait receiving type. | |
// Here the compiler does a little magical coercion for us. | |
// If T is &str then we receive self as &str. | |
// But if T is String then we receive self as &String. | |
// Unlike the other two cases, calling this speak() will never consume self. | |
pub trait Speak { | |
fn speak(self); | |
} | |
impl<T> Speak for &T | |
where | |
T: std::fmt::Display + ?Sized, | |
{ | |
fn speak(self) { | |
println!("{}\nI have spoken.", self); | |
} | |
} | |
// Case 3: Storing the type in a structure. | |
// Makes use of the Borrow trait with a recursive trait bound. | |
// See https://doc.rust-lang.org/std/borrow/trait.Borrow.html | |
// and note the definition of `fn borrow(&self) -> &Borrowed`. | |
// | |
// The trait bound on T essentiall says: | |
// | |
// > I want to store a thing for which I can get a reference to that | |
// > thing, and where that thing also implements Display. | |
// | |
// If T is &str then we store a reference. | |
// If T is String then we consume/take ownership of the String and store it in | |
// the structure. | |
// | |
// Note that Borrow has some special stipulations with regard to hashing. | |
// This is perfectly safe for types using the blanket implementation of Borrow. | |
// But if you want to manually implement the Borrow trait on one of your own | |
// types then be sure to read the documentation first! | |
struct Speaker<T: std::borrow::Borrow<T> + std::fmt::Display> { | |
msg: T, | |
} | |
impl<T: std::borrow::Borrow<T> + std::fmt::Display> Speaker<T> { | |
fn new(msg: T) -> Self { | |
Speaker { msg } | |
} | |
fn speak(&self) { | |
println!("{}\nI have spoken.", self.msg.borrow()); | |
} | |
} | |
fn main() { | |
// Case 1 | |
// Works with anything that implements Display | |
speak(1); | |
// Works with &str | |
let msg: &str = "I can show you to the encampment."; | |
speak(msg); | |
// Works with String | |
let msg: String = "That's where you'll find your quarry.".to_string(); | |
speak(&msg); // msg is borrowed | |
speak(msg); // msg is moved/consumed | |
// speak(msg); // Error: msg was moved on previous line | |
// Works with Box and other Deref types | |
let msg: Box<String> = Box::new("And the blurrgs will join me as well.".to_string()); | |
speak(msg); | |
// Case 2 | |
2.speak(); | |
let msg = "Those that live here come to seek peace."; | |
msg.speak(); | |
let msg = "There will be no peace until they're gone.".to_string(); | |
(&msg).speak(); // Explicitly borrowed as &msg | |
msg.speak(); // Automatic coercion to &msg | |
msg.speak(); // This works because msg was borrowed on previous line | |
let msg = Box::new("Then there will again be peace.".to_string()); | |
msg.speak(); | |
// Case 3 | |
let speaker = Speaker::new(3); | |
speaker.speak(); | |
let msg = "I have never met a Mandalorian."; | |
let speaker = Speaker::new(msg); | |
speaker.speak(); | |
let msg = "I've only read the stories.".to_string(); | |
let speaker = Speaker::new(&msg); // borrowed | |
speaker.speak(); | |
let speaker = Speaker::new(msg); // moved/consumed | |
speaker.speak(); | |
// let speaker = Speaker::new(msg); // Error | |
let speaker = Speaker::new(Box::new("Your ancestors rode the great mythosaur.".to_string())); | |
speaker.speak(); | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment