1 Month Learning Rust - Closure

1 Month Learning Rust - Closure

Daily short news for you
  • These past few days, well not exactly, due to the recent WWDC event, Apple has been the subject of online discussions about where their AI features actually stand. While other companies are diving into bringing AI to their devices and software, Apple seems to be... not too concerned.

    Recently, Apple's researchers suggested that LLM models will "completely collapse in accuracy" when faced with extremely complex problems. By pointing out that reasoning is merely an illusion, many rebuttals to this research emerged immediately. Once again, it raises the question of what Apple is truly thinking regarding AI on their devices.

    I think it's quite simple, Apple seems to be struggling with creating AI for themselves. That is, they are facing difficulties right from the data collection stage for training. They always appear to respect user privacy, so would they really go online to scrape data from everywhere, or "steal" data from users' devices? Surely, they wouldn't want to provide more user data to third parties like OpenAI.

    However, perhaps these challenges will lead them to discover a new direction. If everyone chooses the easy path, who will share the hardships? 😁. Oh, I'm not an "Apple fan," I just use what suits me 🤓.

    » Read more
  • A "sensitive" person to markdown is someone who jumps right in to see what's new when they come across a library that creates a new editor. Milkdown/milkdown is one example.

    Taking a look, it seems quite good, everyone. I might try integrating it into opennotas to see how it goes. It's supposed to be a note-taking application that supports markdown, but the library tiptap doesn't seem to want to add markdown support 😩. Using an external library isn't quite satisfactory yet.

    » Read more
  • Everyone using Cloudflare Worker to call OpenAI's API should be careful, I've encountered the error unsupported_country_region_territory these past few days. It's likely that the Worker server is calling from a region that OpenAI does not support.

    It's strange because this error has only occurred recently 🤔

    » Read more

The Problem

In JavaScript, a Closure refers to a function that can remember and access variables from its outer scope, even after that outer scope has finished executing. In short, when a function is defined inside another function and accesses variables from the parent function, a closure is created.

Example:

function makeCounter() {
  let count = 0; // variable in the scope of makeCounter

  return function() {
    count++; // closure accesses the variable count
    return count;
  };
}

const counter1 = makeCounter(); // Create closure
console.log(counter1()); // 1
console.log(counter1()); // 2

count only exists in makeCounter, but the returned function is still able to access it. The proof is that each time counter1() is called, count increments.

In Rust, closures are also similar to those in JavaScript; they can remember and use variables from the outer scope where they are defined. However, closures in Rust are much more complex than in JavaScript.

Closures in Rust

Closures in Rust are anonymous functions that you can store in a variable or pass as arguments to other functions. You can create a closure in one place and call it in another.

Unlike functions, closures can capture values from the scope they are defined in. Let’s compare a function with a closure.

fn add(a: i32, b: i32) -> i32 {
    a + b
}

fn main() {
    println!("{}", add(1, 2));
}

To call add, you must pass two parameters a and b, and the function returns the sum of those two numbers.

Meanwhile, the closure is written as follows.

fn main() {
    let a = 1;
    let add = |b| a + b;

    println!("{}", add(2)); // 3
}

add is a closure; when calling add, it can access the variable a from the outer environment.

A closure can be declared by assigning it to a variable or calling it directly from the execution point. |b| a + b is a shorthand notation for the closure. The full notation looks like this:

fn main() {
  let a = 1;
  let add = |b: u32| -> u32 {
    a + b
  };

  println!("{}", add(2)); // 3
}

Ownership

Closures can capture values from their environment in three ways:

  • Immutable borrowing.

  • Mutable borrowing.

  • Ownership transfer.

The closure will decide which method to use based on the processing in the body of the closure.

Example.

fn main() {
    let list = vec![1, 2, 3];
    println!("Before defining closure: {list:?}");

    let only_borrows = || println!("From closure: {list:?}");

    println!("Before calling closure: {list:?}");
    only_borrows();
    println!("After calling closure: {list:?}");
}

only_borrows simply prints out the list in list, so this closure only borrows immutably.

Consider the following example.

fn main() {
    let mut list = vec![1, 2, 3];
    println!("Before defining closure: {list:?}");

    let mut borrows_mutably = || list.push(7);

    borrows_mutably();
    println!("After calling closure: {list:?}");
}

borrows_mutably calls a push function on list, changing the original list, so this closure borrows mutably.

A closure takes ownership as shown in the example below.

use std::thread;

fn main() {
    let list = vec![1, 2, 3];
    println!("Before defining closure: {list:?}");

    thread::spawn(move || println!("From thread: {list:?}"))
        .join()
        .unwrap();
}

Fn, FnMut, FnOnce

After a closure has captured a reference or taken ownership of values from the environment where the closure is defined, the code in the body of the closure will determine what happens to the references or values when the closure is executed. A closure can perform any of the following actions: move the captured value out of the closure, change the value, or not affect it at all.

Thus, there are a total of three types of closures implementing the traits Fn, FnMut, and FnOnce, corresponding to the three ways that closures "treat" the ownership of the values from the environment they capture: immutable, mutable, and ownership transfer.

Why implement these three types? Because it determines how closures can be used in certain situations. For example, the sort_by_key method used for sorting lists can only use closures implementing Fn.

#[derive(Debug)]
struct Rectangle {
    width: u32,
    height: u32,
}

fn main() {
    let mut list = [
        Rectangle { width: 10, height: 1 },
        Rectangle { width: 3, height: 5 },
        Rectangle { width: 7, height: 12 },
    ];

    list.sort_by_key(|r| r.width);
    println!("{list:#?}");
}

If you deliberately use a closure with a different trait implementation like FnMut, the program will not run. For example, the following program will report an error cannot move out of value, a captured variable in an FnMut closure.

#[derive(Debug)]
struct Rectangle {
    width: u32,
    height: u32,
}

fn main() {
    let mut list = [
        Rectangle { width: 10, height: 1 },
        Rectangle { width: 3, height: 5 },
        Rectangle { width: 7, height: 12 },
    ];

    let mut sort_operations = vec![];
    let value = String::from("closure called");

    list.sort_by_key(|r| {
        sort_operations.push(value);
        r.width
    });
    println!("{list:#?}");
}

Conclusion

Closures in Rust, although conceptually similar to those in JavaScript, are much more complex. They allow capturing values from the outer scope in three ways: immutable borrowing, mutable borrowing, and ownership transfer, depending on how they are used in the closure body. This is controlled through three main traits: Fn, FnMut, and FnOnce, each suited for different situations. This difference not only makes closures in Rust more flexible but also ensures memory safety and strict ownership management, a core factor in Rust's design.

Premium
Hello

The secret stack of Blog

As a developer, are you curious about the technology secrets or the technical debts of this blog? All secrets will be revealed in the article below. What are you waiting for, click now!

As a developer, are you curious about the technology secrets or the technical debts of this blog? All secrets will be revealed in the article below. What are you waiting for, click now!

View all

Subscribe to receive new article notifications

or
* The summary newsletter is sent every 1-2 weeks, cancel anytime.

Comments (0)

Leave a comment...