Idiomatic Rust Writing concise and elegant Rust code Matthias - - PowerPoint PPT Presentation

idiomatic rust
SMART_READER_LITE
LIVE PREVIEW

Idiomatic Rust Writing concise and elegant Rust code Matthias - - PowerPoint PPT Presentation

Idiomatic Rust Writing concise and elegant Rust code Matthias Endler Dsseldorf, Germany Backend Engineer at Website performance Hot Chocolate matthiasendler mre matthias-endler.de EXPECTATION... REALITY... Python The Zen


slide-1
SLIDE 1

Idiomatic Rust

Writing concise and elegant Rust code

slide-2
SLIDE 2

Düsseldorf, Germany Backend Engineer at Website performance Hot Chocolate

matthiasendler mre matthias-endler.de

Matthias Endler

slide-3
SLIDE 3

EXPECTATION... REALITY...

slide-4
SLIDE 4
slide-5
SLIDE 5

Python

slide-6
SLIDE 6

The Zen f Python

Image: Monty Python and the Holy Grail (1975)

slide-7
SLIDE 7

What is
 idiomatic Rust?

slide-8
SLIDE 8

What is
 idiomatic?

slide-9
SLIDE 9

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

Tim Mansfield

slide-10
SLIDE 10

public bool IsTrue(bool b) { if (b == true) { return true; } return false; }

http://codecrap.com/content/172/

slide-11
SLIDE 11

syntax
 semantics design patterns

Idiomatic Rust

slide-12
SLIDE 12

use rustfmt

???


Idiomatic Rust

syntax
 semantics design patterns rust-unofficial/patterns

slide-13
SLIDE 13
slide-14
SLIDE 14

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

slide-15
SLIDE 15

Case study: Handling money in Rust

slide-16
SLIDE 16

Task:

Parse money, e.g.
 20.42 Dollar or 140 Euro.

slide-17
SLIDE 17

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-18
SLIDE 18

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-19
SLIDE 19

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-20
SLIDE 20

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-21
SLIDE 21

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

slide-22
SLIDE 22

parse_money("140.01 Euro");

thread 'main' panicked at 'called `Result::unwrap()`

  • n an `Err` value: ParseIntError { kind: InvalidDigit

}', src/libcore/result.rs:906:4 note: Run with `RUST_BACKTRACE=1` for a backtrace.

slide-23
SLIDE 23

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-24
SLIDE 24

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-25
SLIDE 25

parse_money("140.01 Euro");

Err(ParseIntError { kind: InvalidDigit })

Bro blem?

slide-26
SLIDE 26

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-27
SLIDE 27

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-28
SLIDE 28

Using float for real-world money objects...

slide-29
SLIDE 29

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-30
SLIDE 30

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

slide-31
SLIDE 31

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-32
SLIDE 32

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-33
SLIDE 33

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-34
SLIDE 34

#[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<ParseFloatError> for MoneyError { fn from(error: ParseFloatError) -> Self { MoneyError::ParseError } }

slide-35
SLIDE 35

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

https://github.com/withoutboats/failure

slide-36
SLIDE 36

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-37
SLIDE 37

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-38
SLIDE 38

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-39
SLIDE 39

fn parse_money(input: &str) -> Result<Money, MoneyError> { 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-40
SLIDE 40

#[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-41
SLIDE 41

#[derive(Debug)] enum Currency { Dollar, Euro, } impl std::str::FromStr for Currency { type Err = MoneyError; fn from_str(s: &str) -> Result<Self, Self::Err> { 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-42
SLIDE 42

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

use own type for money

1 2 3 4 5 6 7 8 9
 10 11 12 13 14

slide-43
SLIDE 43

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

slide-44
SLIDE 44

matthias-endler.de

@matthiasendler

github.com/mre/idiomatic-rust ...use the clippy crate!

Thank you!

Crab title image designed by freepik