Slide 1

Slide 1 text

Idiomatic Rust Writing concise and elegant Rust code

Slide 2

Slide 2 text

! Düsseldorf, Germany ! Backend Engineer at ! Website performance ! Hot Chocolate matthiasendler mre matthias-endler.de Matthias Endler

Slide 3

Slide 3 text

EXPECTATION... REALITY...

Slide 4

Slide 4 text

No content

Slide 5

Slide 5 text

Python

Slide 6

Slide 6 text

The Zen f Python Image: Monty Python and the Holy Grail (1975)

Slide 7

Slide 7 text

Beautiful is better than ugly. Explicit is better than implicit. Zen of Python

Slide 8

Slide 8 text

What is
 idiomatic Rust?

Slide 9

Slide 9 text

What is
 idiomatic?

Slide 10

Slide 10 text

The most concise, convenient and common way of accomplishing a task in a programming language. Tim Mansfield

Slide 11

Slide 11 text

public bool IsTrue(bool b) { if (b == true) { return true; } return false; } http://codecrap.com/content/172/

Slide 12

Slide 12 text

syntax
 semantics design patterns Idiomatic Rust

Slide 13

Slide 13 text

use rustfmt ???
 Idiomatic Rust syntax
 semantics design patterns rust-unofficial/patterns

Slide 14

Slide 14 text

No content

Slide 15

Slide 15 text

https://github.com/mre/idiomatic-rust

Slide 16

Slide 16 text

Case study: Handling money in Rust

Slide 17

Slide 17 text

Task: Parse money, e.g.
 20,42 Dollar or 140 Euro.

Slide 18

Slide 18 text

fn parse_money(input: &str) { let parts: Vec<&str> = input.split_whitespace().collect(); let maybe_amount = parts[0].parse(); if maybe_amount.is_err() { return (-1, "invalid".to_string()); } let currency = parts[1].to_string(); return (maybe_amount.unwrap(), currency); } // TODO 1 2 3 4 5 6 7 8 9

Slide 19

Slide 19 text

fn parse_money(input: &str) -> (i32, String) { let parts: Vec<&str> = input.split_whitespace().collect(); let maybe_amount = parts[0].parse(); if maybe_amount.is_err() { return (-1, "invalid".to_string()); } let currency = parts[1].to_string(); return (maybe_amount.unwrap(), currency); } 1 2 3 4 5 6 7 8 9

Slide 20

Slide 20 text

fn parse_money(input: &str) -> (i32, String) { let parts: Vec<&str> = input.split_whitespace().collect(); let maybe_amount = parts[0].parse(); if maybe_amount.is_err() { return (-1, "invalid".to_string()); } let currency = parts[1].to_string(); return (maybe_amount.unwrap(), currency); } 1 2 3 4 5 6 7 8 9 "magic" error constants

Slide 21

Slide 21 text

use unwrap() fn parse_money(input: &str) -> (i32, String) { let parts: Vec<&str> = input.split_whitespace().collect(); let amount = parts[0].parse().unwrap(); let currency = parts[1].to_string(); return (amount, currency); } 1 2 3 4 5 6

Slide 22

Slide 22 text

parse_money("140 Euro"); (140, "Euro")

Slide 23

Slide 23 text

parse_money("140.01 Euro"); thread 'main' panicked at 'called `Result::unwrap()` on an `Err` value: ParseIntError { kind: InvalidDigit }', src/libcore/result.rs:906:4 note: Run with `RUST_BACKTRACE=1` for a backtrace.

Slide 24

Slide 24 text

fn parse_money(input: &str) -> (i32, String) { let parts: Vec<&str> = input.split_whitespace().collect(); let amount = parts[0].parse().unwrap(); let currency = parts[1].to_string(); return (amount, currency); } 1 2 3 4 5 6 unwrap will panic on error

Slide 25

Slide 25 text

fn parse_money(input: &str) -> Result<(i32, String), ParseIntError> { let parts: Vec<&str> = input.split_whitespace().collect(); let amount = parts[0].parse()?; let currency = parts[1].to_string(); return Ok((amount, currency)); } replace unwrap with ? 1 2 3 4 5 6

Slide 26

Slide 26 text

parse_money("140.01 Euro"); Err(ParseIntError { kind: InvalidDigit }) Bro blem?

Slide 27

Slide 27 text

fn parse_money(input: &str) -> Result<(i32, String), ParseIntError> { let parts: Vec<&str> = input.split_whitespace().collect(); let amount = parts[0].parse()?; let currency = parts[1].to_string(); return Ok((amount, currency)); } Wrong type for parse() 1 2 3 4 5 6

Slide 28

Slide 28 text

fn parse_money(input: &str) -> Result<(f32, String), ParseFloatError> { let parts: Vec<&str> = input.split_whitespace().collect(); let amount = parts[0].parse()?; let currency = parts[1].to_string(); return Ok((amount, currency)); } use float Don't use float for real-world money objects! 1 2 3 4 5 6

Slide 29

Slide 29 text

parse_money("140.01 Euro"); Ok((140.01, "Euro"))

Slide 30

Slide 30 text

thread 'main' panicked at 'index out of bounds: the len is 1 but the index is 1', /Users/travis/build/ rust-lang/rust/src/liballoc/vec.rs:1551:10 note: Run with `RUST_BACKTRACE=1` for a backtrace. parse_money("140.01");

Slide 31

Slide 31 text

fn parse_money(input: &str) -> Result<(f32, String), ParseFloatError> { let parts: Vec<&str> = input.split_whitespace().collect(); let amount = parts[0].parse()?; let currency = parts[1].to_string(); return Ok((amount, currency)); } 1 2 3 4 5 6 Unchecked vector index

Slide 32

Slide 32 text

fn parse_money(input: &str) -> Result<(f32, String), MoneyError> { let parts: Vec<&str> = input.split_whitespace().collect(); if parts.len() != 2 { Err(MoneyError::ParseError) } else { let (amount, currency) = (parts[0], parts[1]); Ok((amount.parse()?, currency.to_string())) } } use custom error 1 2 3 4 5 6 7 8 9

Slide 33

Slide 33 text

#[derive(Debug)]
 pub enum MoneyError {
 ParseError,
 } impl Error for MoneyError {
 fn description(&self) -> &str {
 match *self { MoneyError::ParseError => "Invalid input", } } } impl fmt::Display for MoneyError { fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { match *self { MoneyError::ParseError => f.write_str("Invalid input"), } } } impl From for MoneyError { fn from(error: ParseFloatError) -> Self { MoneyError::ParseError } }

Slide 34

Slide 34 text

#[derive(Debug, Fail)] enum MoneyError { #[fail(display = "Invalid input: {}", _0)]
 ParseAmount(ParseFloatError),
 #[fail(display = "{}", _0)]
 ParseFormatting(String), } impl From for MoneyError { fn from(e: ParseFloatError) -> Self { MoneyError::ParseAmount(e) } } https://github.com/withoutboats/failure

Slide 35

Slide 35 text

println!("{:?}", parse_money("140.01"));
 Err(ParseFormatting("Expecting amount and currency")) println!("{:?}", parse_money("OneMillion Euro"));
 Err(ParseAmount(ParseFloatError { kind: Invalid })) println!("{:?}", parse_money("100 Euro"));
 Ok((100, "Euro"))

Slide 36

Slide 36 text

fn parse_money(input: &str) -> Result<(f32, String), MoneyError> { let parts: Vec<&str> = input.split_whitespace().collect(); if parts.len() != 2 { Err(MoneyError::ParseFormatting( "Expecting amount and currency".into(), )) } else { let (amount, currency) = (parts[0], parts[1]); Ok((amount.parse()?, currency.to_string())) } } explicit length check 1 2 3 4 5 6 7 8 9
 10 11

Slide 37

Slide 37 text

slice patterns fn parse_money(input: &str) -> Result<(f32, String), MoneyError> { let parts: Vec<&str> = input.split_whitespace().collect(); match parts[..] { [amount, currency] => Ok((amount.parse()?, currency.to_string())), _ => Err(MoneyError::ParseFormatting( "Expecting amount and currency".into(), )), } } #![feature(slice_patterns)] 1 2 3 4 5 6 7 8 9
 10

Slide 38

Slide 38 text

fn parse_money(input: &str) -> Result { let parts: Vec<&str> = input.split_whitespace().collect(); match parts[..] { [amount, curr] => Ok(Money::new(amount.parse()?, curr.parse()?)), _ => Err(MoneyError::ParseFormatting( "Expecting amount and currency".into(), )), } } #![feature(slice_patterns)] 1 2 3 4 5 6 7 8 9
 10 use own type for money

Slide 39

Slide 39 text

#[derive(Debug)] struct Money { amount: f32, currency: Currency, } impl Money { fn new(amount: f32, currency: Currency) -> Self { Money { amount, currency } } } use own type for money

Slide 40

Slide 40 text

#[derive(Debug)] enum Currency { Dollar, Euro, } impl std::str::FromStr for Currency { type Err = MoneyError; fn from_str(s: &str) -> Result { match s.to_lowercase().as_ref() { "dollar" | "$" => Ok(Currency::Dollar), "euro" | "eur" | "€" => Ok(Currency::Euro), _ => Err(MoneyError::ParseCurrency("Unknown currency".into())), } } } use own type for money 1 2 3 4 5 6 7 8 9
 10 11 12

Slide 41

Slide 41 text

impl std::str::FromStr for Money { type Err = MoneyError; fn from_str(s: &str) -> Result { let parts: Vec<&str> = s.split_whitespace().collect(); match parts[..] { [amount, currency] => Ok(Money::new(amount.parse()?, currency.parse()?)), _ => Err(MoneyError::ParseFormatting( "Expecting amount and currency".into(), )), } } } use own type for money

Slide 42

Slide 42 text

"140.01".parse::()
 Err(ParseFormatting("Expecting amount and currency")) "OneMillion Bitcoin".parse::() Err(ParseAmount(ParseFloatError { kind: Invalid })) "100 €".parse::() Ok(Money { amount: 100.0, currency: Euro }) "42.24 Dollar".parse::() Ok(Money { amount: 42.42, currency: Dollar })

Slide 43

Slide 43 text

matthias-endler.de github.com/mre/idiomatic-rust ...use clippy! Thank you! Crab title image designed by freepik