Skip to content

Instantly share code, notes, and snippets.

@benkay86
Created February 22, 2021 18:17
Show Gist options
  • Save benkay86/c60b46320a0506c43afa99517d3bd7c6 to your computer and use it in GitHub Desktop.
Save benkay86/c60b46320a0506c43afa99517d3bd7c6 to your computer and use it in GitHub Desktop.
Receiving a borrowed type as either borrowed or owned in Rust
// 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