class: middle, center # syntax conveniences afforded by the Rust compiler Tshepang Lekhonkhobe @tshepang_dev ??? bunch of firsts: - outside Africa - software-related conference - talk So grateful to whoever took a chance on me to be here, also because it's been a dream of mine for years to talk to programming audience, and it's one of those dreams that feel so distant (I am a lucky person). *** There is a constant question about what's the right amount of convenience. The answer varies depending on things like experience (in and outside of Rust). People can also adapt, but this is also complicated by the desire to accomodate both new and old users of a tool. My thought is that "the right amount of convenience" is "something that makes tedious things less so, without hiding things in a way that surprises anyone". This is not what the talk is about... it's more about exploring some the conveniences by rustc, through the use of examples. *** I have a fear that somewhere in the slides I got something wrong, and I would like you to please indicate later on, in any forum or in private. --- class: middle, left # about - I studied electronics formally, but prefer software - I was a Python fan, and now Rust - I work at https://panoptix.co.za, which uses Rust in production since 2016 - https://twitter.com/tshepang_dev ??? - enthusiastic boss took a bet on Rust - not much Rust in Johannesburg, but he looked me up anyways, since I was sort of active in the ecosystem - our meetup hasn't been impressive, but just might, since we are moving it to a a place where it may attract more eyes --- class: middle, center count: false ## looping --- class: middle, center ### desired output ``` 1 2 3 ``` ??? let's say we want code that results in this --- class: middle, left raw ```rust let three = [1, 2, 3]; let count = three.len(); let mut index = 0; loop { if index == count { break; } println!("{}", three[index]); index += 1; } ``` ??? I have come to like Rust syntax, and think the above look good. But it's still laborious, and error-prone, and we have convenient syntax to perform the same thing. --- class: middle, left with __while__ keyword, which offers a bit of sugar ```rust let three = [1, 2, 3]; let count = three.len(); let mut index = 0; while index < count { println!("{}", three[index]); index += 1; } ``` ??? ... this isn't much of an improvement thugh, for we just moved the break-out-the-loop decision maker elsewhere --- class: middle, left with a trait ```rust let three = [1, 2, 3]; let mut it = three.iter(); while let Some(member) = it.next() { println!("{}", member); } ``` ??? much better --- class: middle, left with __for__ keyword, moar sugar ```rust let three = [1, 2, 3]; for member in three.iter() { println!("{}", member); } ``` --- class: middle, left addendum, a functional approach (aka who needs __for__ anyways) ```rust let three = [1, 2, 3]; three.iter().for_each(|member| println!("{}", member)); ``` --- class: middle, center count: false ## range --- class: middle, center ### desired output ``` 1 2 3 ``` ??? same as last time --- class: middle, left there is a type we can use ```rust for n in std::ops::RangeInclusive::new(1, 3) { println!("{}", n); } ``` ??? btw, I so much like inclusive ranges over exclusive one... they are simply more obvious. --- class: middle, left ... or we can take advantage of this convenience ```rust for n in 1..=3 { println!("{}", n); } ``` ??? it's weird syntax actually, and I actually preferred the ..., but many complained that it was not visible enough. --- class: middle, center count: false ## method calls --- class: middle, left A contrived type ```rust pub struct MaxThree { counter: usize, } impl MaxThree { pub fn new() -> Self { Self { counter: 0 } } pub fn increase(&mut self) { if self.counter < 3 { self.counter += 1; } } pub fn current(&self) -> usize { self.counter } } ``` --- class: middle, center ### desired output ```plain loop 0: counter=1 loop 1: counter=2 loop 2: counter=3 loop 3: counter=3 loop 4: counter=3 ``` ??? On the left is iteration steps --- class: middle, left very explicit ```rust let mut max_three = MaxThree::new(); for n in 0..=4 { MaxThree::increase(&mut max_three); // <- look that println!( "loop {}: counter={:?}", n, MaxThree::current(&max_three), // <- look that ); } ``` --- class: middle, left with explicit borrows ```rust let mut max_three = MaxThree::new(); for n in 0..=4 { (&mut max_three).increase(); // <- look that println!( "loop {}: counter={:?}", n, (&max_three).current(), // <- look that ); } ``` --- class: middle, left then, finally... ```rust let mut max_three = MaxThree::new(); for n in 0..=4 { max_three.increase(); println!( "loop {}: counter={:?}", n, max_three.current(), ) } ``` --- class: middle, center count: false ## Self shorthand --- class: middle, left A contrived type (again) ```rust struct Number(i32); impl Number { fn increase(self: &mut Self) { self.0 += 1; } fn increase_elided(&mut self) { self.0 += 1; } } ``` --- class: middle, left They behave exactly the same ```rust let mut number = Number(0); number.increase(); println!("explicit increment: {}", number.0); number.increase_elided(); println!("elided increment: {}", number.0); ``` output ``` explicit increment: 1 elided increment: 2 ``` --- class: middle, left the whole list ```rust self ~-> self: Self &self ~-> self: &Self &mut self ~-> self: &mut Self ``` with familiar surroundings ```rust fn foo(self) ~-> fn foo(self: Self) fn foo(&self) ~-> fn foo(self: &Self) fn foo(&mut self) ~-> fn foo(self: &mut Self) ``` --- class: middle, center count: false ## arithmetic shorthands --- class: middle, left before ```rust let mut foo = 1; foo = foo + 1; assert_eq!(foo, 2); ``` after ```rust let mut foo = 1; foo += 1; // <- look that assert_eq!(foo, 2); ``` --- class: middle, left similarly ```rust let mut foo = 4; foo = foo * 4; assert_eq!(foo, 16); ``` after ```rust let mut foo = 4; foo *= 4; // <- look that assert_eq!(foo, 16); ``` --- class: middle, center count: false ## question mark operator --- class: middle, left before ```rust let content = match fs::read("/etc/os-release") { Ok(content) => content, Err(why) => return Err(why), }; ``` after ```rust let content = fs::read("/etc/os-release")?; // <- look that ``` --- class: middle, left and now for something nested ```rust let content_bytes = match fs::read("/etc/motd") { Ok(content) => content, Err(why) => return Err(why.into()), }; let content_utf8 = match String::from_utf8(content_bytes) { Ok(content) => content, Err(why) => return Err(why.into()), }; ``` --- class: middle, left much better... ```rust let content = String::from_utf8(fs::read("/etc/motd")?)?; ``` ??? You may note that this is contrived, since stdlib got `fs::read_to_string` method. --- class: middle, center count: false ## lifetime elision --- class: middle, left function call before ```rust fn not_elided<'a>(value: &'a str) { println!("{}", value); } ``` ... and after ```rust fn elided(value: &str) { println!("{}", value); } ``` ??? this is ugly notation, so it's kool that we don't have to specify it in obvious cases like one shown here --- class: middle, left **const** ```rust const NOISY: &'static str = "some value"; const ELIDED: &str = "some value" ``` **static** ```rust static NOISY: &'static str = "some value" static ELIDED: &str = "some value"; ``` --- class: middle, center count: false ## type inference, and coercion --- class: middle, left all bare ```rust let value: Vec
= (1_u8..=3_u8).collect(); ``` less type annotating ```rust let value: Vec<_> = (1_u8..=3_u8).collect(); ``` even less annotating ```rust let value: Vec<_> = (1_u8..=3).collect(); ``` you can also just go with fallback int type, `i32` ```rust let value: Vec<_> = (1..=3).collect(); ``` --- class: middle, left addendum, something surprising ```rust let mut value: Vec<_> = (1..=3).collect(); assert_eq!(value, [1, 2, 3_u8]); value.insert(3, 266); println!("{:?}", value); ``` ??? There are probably use-cases where this behavior is desired, and perhaps this example it too contrived for the surprise in real code --- class: middle, left ``` [1, 2, 3, 10] ``` --- class: middle, left There is also... ```rust let mut explicit: Vec
= Vec::new(); explicit.push(0.1_f32); let mut inferred = Vec::new(); inferred.push(0.1_f32); ``` We could also just rely on the float fallback, `f64` ```rust let mut inferred = Vec::new(); inferred.push(0.1); ``` --- class: middle, left ```rust // very explicit... no inference or coercion let foo: i64 = 1_64; // coerced to i8 let foo: i8 = 1; // inferred to i16 let foo = 1; assert_eq!(foo, 1_i16); // inferred to i32, the int fallback let foo = 1; assert_eq!(foo, 1); ``` --- class: middle, left no type annotation (turbofish) needed on `Iter::collect` ```rust fn to_vec(value: &str) -> Vec<&str> { value.split_whitespace().collect() } let value = to_vec("1 2 3"); assert_eq!(value, vec!["1", "2", "3"]); ``` ... but we could if we wanted ```rust value.split_whitespace().collect::
>() ``` --- class: middle, center count: false ## derive --- class: middle, left ### ~~desired~~ expected output ```rust Point { x: 1, y: 2 } ``` (I wish there was a trailing comma, for my eyes) --- class: middle, left imagine ```rust use std::fmt; struct Point { x: isize, y: isize, } impl fmt::Debug for Point { fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { f.debug_struct("Point") .field("x", &self.x) .field("y", &self.y) .finish() } } let point = Point { x: 1, y: 2 }; println!("{:#?}", point); ``` ??? btw when I looked at fmt module, was amazed by the options that it offers... that stuff so featureful. --- class: middle, left more pleasant ```rust #[derive(Debug)] // <- look that struct Point { x: isize, y: isize, } let point = Point { x: 1, y: 2 }; println!("{:#?}", point); ``` ??? This ease also means most people don't need to look at fmt --- class: middle, center count: false ## deref coercion --- class: middle, left setup ```rust use std::{ ffi::OsStr, ops::Deref, path::{Path, PathBuf}, }; let borrowed = Path::new("some/path.txt"); let owned = PathBuf::from("some/path.txt"); ``` --- class: middle, left we can do all these ```rust assert_eq!(owned, borrowed); assert_eq!(&owned, borrowed); // another way to peel avo assert_eq!(owned, *borrowed); // yet another way to peel avo ``` also ```rust fn ext(value: &Path) -> Option<&OsStr> { value.extension() } assert_eq!(ext(&owned), ext(borrowed)); assert_eq!(ext(owned.deref()), ext(borrowed)); assert_eq!(ext(owned.as_path()), ext(borrowed)); assert_eq!(ext(&owned), Some(OsStr::new("txt"))); ``` --- class: middle, center count: false ## if let --- class: middle, left ```rust let maybe = (1..3).next(); // before match maybe { Some(value) => println!("{:?}", value), None => (), }; // after if let Some(value) = maybe { println!("{:?}", value); }; ``` --- class: middle, center count: false ## pretty imports --- class: middle, left you could do this ```rust use std::fs; use std::path; use std::path::Path; use std::path::PathBuf; use std::time::Duration; ``` or this ```rust use std::{fs, path::{self, Path, PathBuf}, time::Duration}; ``` maybe this even ```rust use std::{ fs, path::{ self, Path, PathBuf, }, time::Duration, }; ``` ??? you saw example of the last style earlier --- class: middle, left # thanks - Rust in Ten Slides (https://github.com/steveklabnik/rust-in-ten-slides) - RustFest Paris 2018, for inspiration (https://paris.rustfest.eu) - Rust team (https://rust-lang.org/team) ??? thanks to steved for having a look at a draft, and resulting suggestions btw, I will be in Rome next month