skip to content
Ben Lau statistics . machine learning . programming . optimization . research

Rust

6 min read Updated:

Prototyping

  • Feel free to use unwrap and expect methods when prototyping, before you have a clear idea of how you want to handle errors. They leave clear markers in your code for when you’re ready to make your program more robust. rust book
  • Return Result<(), Box<dyn Error>> from main function so that you can use ? operator in main function. It allows easy refactoring when you want to encapsulate some code blocks into functions.

Error handling

  • A Simpler Way to See Results
    • How to use ?
      • How to define a function with multiple possible errors which may be populated by ?
        • anyhow::Error, which is similar but a better Box<dyn Error>
        • enum Error + thiserror which helps to implement the From trait
    • When to use Result(T, E), when to use Option(T)
    • Infallible (bottom type) uses !

Refactoring

Rust is fantastic for refactoring, the type system with the compiler will guide you until you get it right, while other languages will not let you know that if you finish or not, and will certainly just break at runtime. Once you start refactoring, the compiler surfaces the problems early and generates a todo list for you. It is time consuming, but its correctness is guaranteed. discussion

Testing

Concurrency and Parallelism

  • async and await give us a way to write asynchronous code. By awaiting a async function, we can run the function in sequential order, just like synchronous code, but without blocking the thread. However, it does not bring any performance benefits unless we bring in concurrency by using task::spawn or futures::join! or for_each_concurrent etc. Iteration and Concurrency Properly using async ? How to take advantage of it ?@reddit
  • without await in a async function while using tokio::join! or tokio::spawn!, the function will not be executed concurrently because the tokio runtime is not able to swap the tasks, which can only happen at an await, i.e. those tasks are blocking. Async: What is blocking?
  • Parallelism vs concurrency vs mixed in rust with examples
  • Sometimes blocking is good, for example, for a CPU-bound task or synchronous I/O. Parallelism by rayon could be a better choice for CPU-bound tasks, e.g. computation.
  • If a blocking operation keeps running forever, you should run it on a dedicated thread, for example, a web server that listens to incoming requests that may come at any time.
  • Returning a stream does not need to be async

Readings

trait object vs enum

static dispatch vs dynamic dispatch

  • “When you’re given the choice between static and dynamic dispatch, there is rarely a clear-cut right answer. Broadly speaking, though, you’ll want to use static dispatch in your libraries and dynamic dispatch in your binaries. In a library, you want to allow your users to decide what kind of dispatch is best for them, since you don’t know what their needs are. If you use dynamic dispatch, they’re forced to do the same, whereas if you use static dispatch, they can choose whether to use dynamic dispatch or not.” - Rust for Ruataceans

trait object vs generic trait bound or impl Trait

Why slice is DST but not the vector

memory-management#Dynamically sized type (DST) in rust

Scope example

In the following example, stmt isn’t dropped until the end of the scope. tx.commit will take ownership of self, so tx will be moved then stmt cannot drop the borrow of tx, causing error. So we need to create a inner scope to drop it this inner scope is for dropping stmt before running tx.commit(). ref

struct File {
file_name: String,
modified_at: String,
size: i32,
}
let files = vec![
File {
file_name: "file1".to_string(),
modified_at: "2024-09-20".to_string(),
size: 100,
},
File {
file_name: "file2".to_string(),
modified_at: "2024-09-20".to_string(),
size: 200,
},
];
let mut conn = Connection::open("file.db")?;
// transaction is created because it avoids the overhead of committing after each insert
let tx = conn.transaction()?;
{
let mut stmt = tx.prepare(
r#"INSERT INTO files (file_name, modified_at, size) VALUES (?, ?, ?)"#,
)?;
for file in files {
stmt.execute([
&file.file_name,
&file.modified_at,
&file.size.to_string(),
])?;
}
}
tx.commit()?;