$30 off During Our Annual Pro Sale. View Details »

Idiomatic Rust - Writing concise and elegant Rust code

Idiomatic Rust - Writing concise and elegant Rust code

Rust is a big language and it gets bigger every day. Many beginners ask: "What is idiomatic Rust?". This talk will highlight simple tips to make your Rust code more elegant and concise, and introduce you to my peer-reviewed collection of articles/talks/repos for writing idiomatic Rust code.

Coming from dynamic languages like Python, JavaScript or Ruby, many Rust beginners are missing some guidelines on how to write elegant and concise Rust code. For this purpose, I started a project called "Idiomatic Rust", which is a peer-reviewed collection of articles/talks/repos which teach the essence of good Rust.

In this talk I will introduce the project and show you some quick tips on how to make your Rust code more idiomatic. I will cover error handling (e.g. Option to Result conversions, the failure crate), efficiently working with (built-in) traits, and some more.

Matthias Endler

February 04, 2018
Tweet

More Decks by Matthias Endler

Other Decks in Programming

Transcript

  1. Idiomatic Rust
    Writing concise and elegant Rust code

    View Slide

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

    View Slide

  3. EXPECTATION...
    REALITY...

    View Slide

  4. View Slide

  5. Python

    View Slide

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

    View Slide

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

    View Slide

  8. What is

    idiomatic Rust?

    View Slide

  9. What is

    idiomatic?

    View Slide

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

    View Slide

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

    View Slide

  12. syntax

    semantics
    design patterns
    Idiomatic Rust

    View Slide

  13. use rustfmt
    ???

    Idiomatic Rust
    syntax

    semantics
    design patterns rust-unofficial/patterns

    View Slide

  14. View Slide

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

    View Slide

  16. Case study: Handling money in Rust

    View Slide

  17. Task:
    Parse money, e.g.

    20,42 Dollar or 140 Euro.

    View Slide

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

    View 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

    View Slide

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

    View Slide

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

    View Slide

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

    View Slide

  23. 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.

    View Slide

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

    View Slide

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

    View Slide

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

    View Slide

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

    View Slide

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

    View Slide

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

    View Slide

  30. 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");

    View Slide

  31. 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

    View Slide

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

    View Slide

  33. #[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
    }
    }

    View Slide

  34. #[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

    View Slide

  35. 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"))

    View Slide

  36. 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

    View Slide

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

    View Slide

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

    View Slide

  39. #[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

    View Slide

  40. #[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

    View Slide

  41. 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

    View Slide

  42. "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 })

    View Slide

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

    View Slide