The Rust Deep Learning Framework That's Hotter Than Dragonfire (But Friendlier)
Today's Issue: Taming the eager unwrap_or(), Type-Driven API Design in Rust, and Oxidize Conf 2024
Hello Rustacean! Welcome to another edition of the Rust Bytes newsletter. In this issue, we'll shine a spotlight on an amazing Rust project, present our Rust Quiz, and share some incredible links of the week.
Welcome to Issue 23!
The Main Thing
Safe unwrap_or
Usage in Rust
Rust's Option enum type offers a safe and expressive approach to representing the absence of a value. We can propagate such errors from an Option enum and handle them appropriately using methods such as unwrap().
While unwrap can be used to extract the value from a Some variant, it panics if the variant is None. unwrap_or() offers a safer alternative, allowing you to handle both cases.
Value Present: If the Option holds a value (Some(value)), unwrap_or() simply returns that value.
Value Absent: When the Option is None, unwrap_or() provides a default value you specify.
unwrap_or() is a double edged sword and can cause chaos if used carelessly. Lets look into the hidden dangers of unwrap_or(), particularly when used with function calls within its closure.
The Illusory Safety Net
Imagine you're building a program that fetches data from a network. You might use a function like get_data_from_server() that could return either the retrieved data (Some(data)) or None if the request fails.
Here's where unwrap_or() might seem like a perfect fit:
let data = get_data_from_server().unwrap_or(|| {
// Handle the case where data is None
// (e.g., display an error message)
});
At first glance, this appears elegant. If data is available, it's used. Otherwise, the closure executes, potentially displaying an error message. However, there's a crucial detail to remember:
The Eager Execution Trap
The key problem lies in how unwrap_or() behaves. It eagerly evaluates the entire closure you provide, regardless of whether the value being unwrapped is Some or None.
This might seem harmless, but consider a scenario where the closure itself involves function calls.
Function Calls and the Butterfly Effect
Let's say your closure calls a function do_something_important(). This function, unbeknownst to you, might have side effects – actions that modify the program's state beyond simply returning a value. Here's the twist:
let data = get_data_from_server().unwrap_or(|| {
do_something_important(); // This function call might have unintended side effects!
});
Even if get_data_from_server() successfully retrieves data (Some(data)), the closure with do_something_important() will still be executed! This can lead to unexpected consequences, especially if do_something_important()'s side effects are not what you intended for this specific code path.
The potential consequences of this eager evaluation are vast. Imagine do_something_important() accidentally modifying a global variable or triggering unintended network requests. In a complex system, these side effects can be difficult to trace and debug, causing errors that may not be immediately apparent.
Safer Alternatives
To avoid the pitfalls of unwrap_or() with function calls, consider these safer approaches:
Match Expressions:
match get_data_from_server() {
Some(data) => data,
None => {
// Handle the case where data is None
// (e.g., display an error message)
}
}
if let
withelse
:
if let Some(data) = get_data_from_server() {
data
} else {
// Handle the case where data is None
// (e.g., display an error message)
}
These options explicitly check for Some or None and execute the appropriate code block, ensuring function calls only happen when necessary.
Remember that unwrap_or() remains a valuable tool, but use it cautiously, especially when function calls are involved.
Challenge: Rust Quiz
What will be the output?
fn main() {
let mut rust_bytes = String::from("Rust Bytes");
let rust_bytes_ref = &mut rust_bytes;
rust_bytes.push_str(" is great.");
println!("{}", rust_bytes_ref);
}
A. Rust Bytes is great.
B. Compiler Error
Project Spotlight
Burn: The Rust Deep Learning Framework That's Hotter Than Dragonfire (But Friendlier)
Introducing Burn, the dynamic Deep Learning Framework built with Rust, designed to be:
Blazing Fast: We're talking automatic kernel fusion, asynchronous execution, and a whole lot of optimization magic. 💥
Super Flexible: Need to run your model on a toaster, a supercomputer, or a potato? Burn's got your back (or whatever appendage your potato has). 🌐
Work with existing tools: Import PyTorch models and use ONNX support for seamless integration. 🐫
Deploy with ease: Burn makes the transition from training to deployment smoother like never before.
Burn is open-source: Peek at the code, fork, tinker, and contribute your brilliance.
Awesome links of the week 🔗
Rustup v1.27.1 got released 🚀 Update now for minor bug fixes and improved compatibility, to ensure a smoother Rust development experience.
Keith T. Star blogged about Plugins in Rust.
Arsh wrote an informative article about generating prime numbers, particularly focusing on the challenge of generating 1024-bit primes for RSA keys.
Oxidize Conf 2024 is back and its running from May 28th-30th in Berlin. Don't miss out - grab your conference tickets.
Naz Ahmed wrote Speeding up Rust compilations on macOS.
Type-Driven API Design in Rust by Will Crichton is a must-watch for any
typeceanRustacean. 📹Robin wrote about the inner workings of a very basic custom memory allocator written in Rust.
Sanjiv Sahayam wrote a comprehensive 14-part series on Working with Rust Result.
The Algorithms is a collection of all algorithms implemented in Rust.
In other news, Stack Overflow has announced a partnership with OpenAI to strengthen the world’s most popular large language models.
Challenge: Solution
Correct Answer:
B. Compilation Error.
`rust_bytes_ref` is a mutable reference to the String `rust_bytes`. This is the first mutable reference to the String type.
Let’s look at the signature of `push_str` method:
pub fn push_str(&mut self, string: &str) {
self.vec.extend_from_slice(string.as_bytes())
}
This method needs a mutable reference. So, the invocation of this method results in another mutable reference to the same string. Rust does not allow multiple mutable references to a type in the same scope.
Solutions:
Option 1:
Remove the mutable reference `rust_bytes_ref`.
Option 2:
Restructure the code and create a separate method that takes the mutable reference and pushes into the string.
fn main() {
let mut rust_bytes = String::from("Rust Bytes");
push(&mut rust_bytes);
let rust_bytes_ref = &mut rust_bytes;
println!("{}", rust_bytes_ref);
}
fn push(str: &mut String) {
str.push_str(" is great.");
}
Before You Go
To support the newsletter:
❤️ Recommend Rust Bytes to your friends: it really helps.
🤳 Check us out on our socials: X, Rustaceans Publication.
📨 Contact us through rustaceanseditors@gmail.com feedback is welcome.
💸 Sponsor the newsletter.
That's all for now, Rustaceans! Until next issue, have a productive week ahead.