Skip to content

Latest commit

 

History

History
400 lines (294 loc) · 12.8 KB

rust.md

File metadata and controls

400 lines (294 loc) · 12.8 KB

Rust

The following conventions help keeping overview and the code clear. Since there exists an official Rust Style Guide, this file just adds or summarizes some useful info. Keep the guiding principles and rationale in mind when coding rust.

  1. File style
  2. Some coding details
    1. Types
    2. References
    3. Copy vs Clone
    4. Strings
    5. Error handling
  3. Coding Conventions
  4. Documentation
  5. Howto setup a complex project
    1. mod vs use
    2. Module and folder structure
    3. The Manifest Format
  6. Nice stuff
    1. Implement slice for custom type

File style

  • Maximum line width is 100. Exceptions can be made for String-constants and similar.

    This is a good trade off between 120 and 80. Humans have trouble reading the code with increasing line width. In general, more than 80 is not recommended.

  • Use 4 spaces for indention (p.s.: could help your salary!).

Some coding details

Types

To get the type of a variable, consider "asking" the compiler by explicitly setting the type of a variable to ().

let my_number: () = 3.4;
// compiler:        ^^^ expected (), found floating-point variable

References

Following code blocks from rust-lang show two identical lines.

ref on the left side of = is the same as & on the right side.

let ref x = 1;
let x = &1;

& on the left side of = is the same as * on the right side.

let &y = x;
let y = *x;

Copy vs Clone

From stack overflow:

The main difference is that cloning is explicit. Implicit notation means move for a non-Copy type.

// u8 implements Copy
let x: u8 = 123;
let y = x;
// x can still be used
println!("x={}, y={}", x, y)

// Vec<u8> implements Clone, but not Copy
let v: Vec<u8> = vec![1, 2, 3];
let w = v.clone();
//let w = v // This would *move* the value, renderinng v unusable.

Strings

Raw string literals allow writing escape characters without \.

Visualizing raw string literals

Example:

let toml = r#"
    [package]
    name = "osmgraphing"
    version = "0.1.0"
    authors = [
        "dominicparga <[email protected]>",
        "jenasat <[email protected]>"
    ]
    edition = "2018"

    [dependencies]
    quick-xml = "*"
"#;

Error handling

The official Rust docs say:

  • An explicit panic is mainly useful for tests and dealing with unrecoverable errors. It may help with prototyping, but unimplemented is better.
  • The Option type is for when a value is optional or when the lack of a value is not an error condition. unwrap may be used, but expect should be preferred.
  • The Result type is used when there is a chance that things do go wrong and the caller has to deal with the problem. Although unwrap and expect is provided, don't use it (unless it's a test or quick prototype).

This citation from this thread on reddit adds some info wrt. panic!() vs Result, Option:

panic!(), expect("my random logic error") and .unwrap() should only be used when:

  • An irrecoverable error, such as out-of-memory, under which scenario it would be unreasonable for the program to continue execution.
  • For the normal operation of testing frameworks such as proptest.
  • When you are sure the panic will never happen and when it would be a programmer logic error otherwise.

Otherwise, you should prefer Result<T, E>, Option<T>, or similar mechanismus to handle errors due to user action. To make this ergonomic, you should use the ?-operator.

Coding Conventions

  • These official naming conventions should be used.

  • Use constants over magic numbers!

    Even you as the author will not know the meaning of every number after several months. And if you know, you will probably forget the precision of your constant and the places, where you put them (-> bad for debugging).

    // BAD
    
    let area = 3.1 * radius * radius;
    // (...)
    // somewhere else in the code
    let circum = 2 * 3.1415 * radius;
    // or
    let circum = 6.283 * radius;
    
    
    
    // GOOD
    
    // maybe inside a wrapper struct?
    let PI   = 3.1415;
    let PI_2 = 6.2832;
    
    let area = PI * radius * radius;
    // (...)
    // somewhere else in the code
    let circum = 2 * PI * radius;
    // or
    let circum = PI_2 * radius;
  • Use white spaces around binary operators. Exceptions can be made for special cases to improve readability (see below).

    - let e = (- a) * b;
    + let e = (-a) * b;
    
    - let e = a*b;
    + let e = a * b;
    
    + let e = a * b + c * d;    // ok, but not recommended here
    + let e = a*b + c*d;        // improves readability
  • In general, use return statements only in early returns. Rust is an expression-oriented language, which means every chunk of code has a return value (e.g. match, for, ...). Hence it's clear, that the last code snippet of a code block is the block's value.

Documentation

Rust provides pretty code by design due to its namespaces, modules and its clean and powerful project setup (Cargo.toml). If you still need to separate code sections, you may use //------- (whole line), but due to rusts impl-blocks, this approach also can get a little ugly. Otherwise, take the following code example from Rust's official documentation.

//! A doc comment that applies to the implicit anonymous module of this crate

pub mod outer_module {

    //!  - Inner line doc
    //!! - Still an inner line doc (but with a bang at the beginning)

    /*!  - Inner block doc */
    /*!! - Still an inner block doc (but with a bang at the beginning) */

    //   - Only a comment
    ///  - Outer line doc (exactly 3 slashes)
    //// - Only a comment

    /*   - Only a comment */
    /**  - Outer block doc (exactly) 2 asterisks */
    /*** - Only a comment */

    pub mod inner_module {}

    pub mod nested_comments {
        /* In Rust /* we can /* nest comments */ */ */

        // All three types of block comments can contain or be nested inside
        // any other type:

        /*   /* */  /** */  /*! */  */
        /*!  /* */  /** */  /*! */  */
        /**  /* */  /** */  /*! */  */
        pub mod dummy_item {}
    }

    pub mod degenerate_cases {
        // empty inner line doc
        //!

        // empty inner block doc
        /*!*/

        // empty line comment
        //

        // empty outer line doc
        ///

        // empty block comment
        /**/

        pub mod dummy_item {}

        // empty 2-asterisk block isn't a doc block, it is a block comment
        /***/

    }

    /* The next one isn't allowed because outer doc comments
       require an item that will receive the doc */

    /// Where is my item?
  mod boo {}
}

Howto setup a complex project

mod vs use

While mod declares a module, use reduces verbose code by bringing namespaces into scope. For more information, see here.

pub mod a {
    pub mod b {
        pub fn some_fn() {
            // ...
        }
    }
}

use a::b;

// bad style
// use a::b::somefn;

fn main() {
    b::some_fn();
    // instead of a::b::some_fn();
}

Module and folder structure

Most of the following folder tree is from Rust's Manifest Format doc. Information about visibility can be found here.

project_name
├── src/               # directory containing source files
│   ├── lib.rs         # the main entry point for libraries and packages
│   ├── main.rs        # the main entry point for packages producing executables
│   ├── bin/           # (optional) directory containing additional executables
│   │   └── *.rs
│   └── */             # (optional) directories containing multi-file executables
│       └── main.rs
├── examples/          # (optional) examples
│   ├── *.rs
│   └── */             # (optional) directories containing multi-file examples
│       └── main.rs
├── tests/             # (optional) integration tests
│   ├── *.rs
│   └── */             # (optional) directories containing multi-file tests
│       └── main.rs
└── benches/           # (optional) benchmarks
    ├── *.rs
    └── */             # (optional) directories containing multi-file benchmarks
        └── main.rs

Prefer package/folder/file management over class mangement if meaningful.
BUT: Think in an intuitive, handy and deterministic(!) way and don't take structuring and subfolding too far.

Always ask yourself:
How would most of the people search for this module/struct/file?
Someone without knowing your whole project structure should be able to find a file at the first try.
In every folder, there should be only one option to continue searching (-> determinism).

Besides that, Rust provides other nice ways of managing source files. One approach shows a submodule module_a as a file.

project_name
├── src/
│   ├── module_a.rs
│   └── ...
└── ...
// project_name/src/module_a.rs
pub mod module_a {
    pub mod nice_content {
        // ...
    }
}

It is possible to split the module into multiple files easily as shown below.

project_name
├── src/
│   ├── module_a
│   │   └── mod.rs
│   └── ...
└── ...
// project_name/src/module_a/mod.rs
pub mod nice_content {
    // ...
}

In both ways, main.rs can access the modules in the same way.

// project_name/src/main.rs
mod module_a;

fn main() {
    // access module_a::nice_content
}

The Manifest Format

A very nice documentation about Rust's Manifest Format is provided here.

Nice stuff

Implement slice for custom type