class: center, middle # Reference Types --- name: topics # Topics [Background](https://epage.github.io/talks/2019/09/ref/#background) [API Design](https://epage.github.io/talks/2019/09/ref/#api) [serde](https://epage.github.io/talks/2019/09/ref/#serde) --- name: background class: middle # Background --- # Background ```python def extract_first_column(content: str) -> str: lines = [line.split(" ", 1)[0] for line in content.splitlines()] content = "\n".join() return content content = path.read_text() content = extract_first_column(content) ``` --- # Background ```python def extract_first_column(content: str) -> str: lines = [line.split(" ", 1)[0] for line in content.splitlines()] content = "\n".join() return content content = path.read_text() content = extract_first_column(content) ``` From Rust's perspective: ```rust fn extract_first_column(content: Arc
) -> Arc
: let lines: impl Iterator
> = content.lines(); let lines = lines.map(|s| s.splitn(2, ' ').next().unwrap()); itertools::join("\n", lines) let content = std::fs::read_to_string(path.clone())?; let content = extract_first_column(content.clone()); ``` ??? - `Arc
` used with `.clone()` everywhere - `.split` requires copying substrings into new `Arc
` - String literals implicitly create `Arc
` --- # Background ```rust fn extract_first_column(content: Arc
) -> Arc
: let lines: impl Iterator
> = content.lines(); let lines = lines.map(|s| s.splitn(2, ' ').next().unwrap()); itertools::join("\n", lines) let content = std::fs::read_to_string(path.clone())?; let content = extract_first_column(content.clone()); ``` Idiomatic Rust: ```rust fn extract_first_column(content: &str) -> String: let lines = content.lines().map(|s| s.splitn(2, ' ').next().unwrap()); itertools::join("\n", lines) let content = std::fs::read_to_string(path)?; let content = extract_first_column(&content); ``` ??? - Only allocates at `read_to_string` and `itertools::join` - No ref counting Made possible by `&str` - `&str` can reference both string literals and `String` - `&str` can reference substrings --- # Examples of Owned/Borrowed Types - `String` / `&str` - `PathBuf` / `&Path` - `OsString` / `&OsStr` - `Vec` / `&[]` --- # Abstracting over Owned/Borrowed ```rust pub enum Cow<'a, B> where B: 'a + ToOwned + ?Sized, { Borrowed(&'a B), Owned(
::Owned), } ``` - `into_owned()` - `as_ref()` - `to_mut()` --- name: api class: middle # API Design --- # Which to accept? ```rust pub struct Builder { name: String, } impl Builder { * // Should `name` be `String` or `&str`? * pub fn name(&mut self, name: ?) -> &mut Self { self.name = name; self } } ``` --- # Which to accept? ```rust pub struct Builder { name: String, } impl Builder { * pub fn name(&mut self, name: String) -> &mut Self { self.name = name; self } } *let foo: String = format!("Hello {}!", 42); Builder::new().name(foo) ``` ??? - If need a `String`, do you avoid `.clone()` if the caller has one to give you? - Are you exposing your implementation details? --- # Which to accept? ```rust pub struct Builder { name: String, } impl Builder { pub fn name(&mut self, name: String) -> &mut Self { * self.name = normalize_newlines(&name); self } } let foo: String = format!("Hello {}!", 42); Builder::new().name(foo) ``` --- # Which to accept? ```rust pub struct Builder { name: String, } impl Builder { pub fn name(&mut self, name: String) -> &mut Self { self.name = normalize_newlines(&name); self } } *let foo: &str = "Hello 42"; *Builder::new().name(foo.to_owned()) ``` --- # Accepting Either ```rust pub struct Builder { name: String, } impl Builder { * pub fn name(&mut self, name: impl Into
) -> &mut Self { * self.name = name.into(); self } } ``` ```rust pub struct Builder { name: String, } impl Builder { * pub fn name(&mut self, name: impl AsRef
) -> &mut Self { * self.name = normalize_newlines(name.as_ref()); self } } ``` ??? Technically it is a breaking change to switch the interface but it is one that will have negligible impact. --- # Reducing binary bloat ```rust pub struct Builder { name: String, } impl Builder { pub fn name(&mut self, name: impl Into
) -> &mut Self { self.name_internal(name.into()) } fn name_internal(&mut self, name: String) -> &mut Self { self.name = name.into(); self } } ``` ??? Not needed for functions this trivial but something to keep in mind. --- name: serde class: middle # serde --- # serde ```rust /// A line of code associated with the Diagnostic #[derive(Debug, Clone, Serialize, Deserialize)] pub struct DiagnosticSpanLine<'a> { /// The line of code associated with the error * pub text: String, /// Start of the section of the line to highlight. 1-based, character offset in self.text pub highlight_start: usize, /// End of the section of the line to highlight. 1-based, character offset in self.text pub highlight_end: usize, } ``` ```json {"text": "foo", "highlight_start": 0, "highlight_end": 2} ``` --- # serde ```rust /// A line of code associated with the Diagnostic #[derive(Debug, Clone, Serialize, Deserialize)] pub struct DiagnosticSpanLine<'a> { /// The line of code associated with the error * #[serde(borrow)] * pub text: &'a str, /// Start of the section of the line to highlight. 1-based, character offset in self.text pub highlight_start: usize, /// End of the section of the line to highlight. 1-based, character offset in self.text pub highlight_end: usize, } ``` ```json {"text": "foo", "highlight_start": 0, "highlight_end": 2} ``` ??? - Makes it more annoying for passing around in application --- # serde ```rust /// A line of code associated with the Diagnostic #[derive(Debug, Clone, Serialize, Deserialize)] pub struct DiagnosticSpanLine<'a> { /// The line of code associated with the error #[serde(borrow)] pub text: &'a str, /// Start of the section of the line to highlight. 1-based, character offset in self.text pub highlight_start: usize, /// End of the section of the line to highlight. 1-based, character offset in self.text pub highlight_end: usize, } ``` ```json *{"text": "foo\nbar", "highlight_start": 0, "highlight_end": 2} ``` ??? - Errors if conversion is required --- # serde ```rust /// A line of code associated with the Diagnostic #[derive(Debug, Clone, Serialize, Deserialize)] pub struct DiagnosticSpanLine<'a> { /// The line of code associated with the error * #[serde(borrow)] * pub text: std::borrow::Cow<'a, str>, /// Start of the section of the line to highlight. 1-based, character offset in self.text pub highlight_start: usize, /// End of the section of the line to highlight. 1-based, character offset in self.text pub highlight_end: usize, } ``` ```json {"text": "foo\nbar", "highlight_start": 0, "highlight_end": 2} ``` ??? - Works in all cases - No longer `Copy` - Annoying to convert between `Owned` and `Borrowed` --- # serde ```rust /// A line of code associated with the Diagnostic #[derive(Debug, Clone, Serialize, Deserialize)] pub struct DiagnosticSpanLine<'a> { /// The line of code associated with the error * #[serde(borrow)] * pub text: std::borrow::Cow<'a, str>, /// Start of the section of the line to highlight. 1-based, character offset in self.text pub highlight_start: usize, /// End of the section of the line to highlight. 1-based, character offset in self.text pub highlight_end: usize, } ``` `std::borrow::Cow::Owned`: ```json {"text": "foo\nbar", "highlight_start": 0, "highlight_end": 2} ``` `std::borrow::Cow::Borrowed`: ```json {"text": "foo", "highlight_start": 0, "highlight_end": 2} ``` --- background-image: url(http://giphygifs.s3.amazonaws.com/media/tnYri4n2Frnig/giphy.gif) class: center, middle