[Rust] Play around with Rust

发布时间 2023-05-09 01:11:37作者: Zhentiw

Few intertesting things from Rust

  • Option<T>: with Some, unwrap() function
  • todo(): to skil compiler error for a while
  • unreachable(): similar to Typescript, asserts never.
  • vec vs array
  • String vs &str
  • Struct, Impl
  • traits

 

Option<T>, similar to Maybe, Either

In Rust, Option is an enum that represents the possibility of a value being present (Some) or absent (None). The unwrap() method is used to extract the value from a Some variant or panic if the Option is None

fn main() {
    let maybe_number: Option<i32> = get_number();

    // Using unwrap() to get the value from the Option
    let number = maybe_number.unwrap();

    println!("The number is: {}", number);
}

fn get_number() -> Option<i32> {
    // Return Some(value) if you have a value to return
    Some(42)

    // Return None if there is no value to return
    // None
}

Keep in mind that using unwrap() can be risky since it will cause your program to panic if the Option contains None.

It's generally better to use pattern matching or methods like unwrap_or, unwrap_or_default, or map to handle Option in a more graceful way.

 

unwrap_or: set default value if is None

fn main() {
    let maybe_number: Option<i32> = get_number();
    
    // Using unwrap_or() to get the value from the Option or use a default value
    let number = maybe_number.unwrap_or(0);

    println!("The number is: {}", number);
}

fn get_number() -> Option<i32> {
    // Return Some(value) if you have a value to return
    // Some(42)

    // Return None if there is no value to return
    None
}

 

unwrap_or_default: set a defualt value by impl#[derive(Debug)]

#[derive()] is an attribute used to automatically generate certain trait implementations for a struct or enum. In the example provided, #[derive(Debug, Default)] is used to automatically generate the implementations for the Debug and Default traits for the Person struct

#[derive(Debug)]
struct Person {
    name: String,
    age: u32,
}

impl Default for Person {
    fn default() -> Self {
        Person {
            name: String::from("John Doe"),
            age: 18,
        }
    }
}


fn main() {
    let maybe_person: Option<Person> = get_person();
    
    // Using unwrap_or() to get the value from the Option or use a default value
    let person = maybe_person.unwrap_or_default();

    println!("The person is: {:?}", person); // The person is: Person { name: "John Doe", age: 18 }
}

fn get_person() -> Option<Person> {
    None
}

 

todo & unreachable

fn only_evens(x: usize) -> bool {
    if x % 2 == 1 {
        unreachable!("this should never happen")
    }
    
    // without todo: `if` may be missing an `else` clause
    todo!("TODO: come to this part later")
}

fn main() {
  only_evens(10)
}

 

vec vs array

In Rust, arrays have a fixed size, so you can't change the size directly. However, you can use a Vec (vector) instead to achieve a similar effect. 

fn main() {
    let foo = vec![1, 2, 3];
    let foo = someMethod(foo);
}

fn someMethod(mut input: Vec<i32>) -> Vec<i32> {
    // Modify the input vector as needed
    input.push(4); // Adding 4 to the vector as an example
    input
}

need to use mut, otherwise, you cannot call .push().

 

String vs &str

String is a mutable, heap-allocated string type that owns its memory, while &str is an immutable reference to a string that doesn't own the memory it points to. Use String when you need to modify or build a string dynamically, and use &str when you need to pass a read-only reference to a string, such as in function parameters.

Creating string:

fn main() {
    let s1 = String::from("Hello, world!"); // Creates a String
    let s2: &str = "Hello, world!"; // Creates a string slice

    println!("s1: {}", s1);
    println!("s2: {}", s2);
}

 

Passing a `&str` to a function:

fn main() {
    let name = String::from("Alice");
    greet(&name); // Passing a string slice to the greet function
}

fn greet(name: &str) {
    println!("Hello, {}!", name);
}

 

Modify a String:

fn main() {
    let mut s = String::from("Hello");
    s.push_str(", world!");
    
    println!("s: {}", s);
}

 

Creating a string slice from a `String`

fn main() {
    let s = String::from("Hello, world!");
    let hello = &s[0..5]; // Creating a string slice from a String

    println!("hello: {}", hello);
}

 

Trait, Impl, Struct

  • Traits in Rust are used to define shared behavior across types. They can define methods, associated functions, and associated types.
  • Rust uses nominal typing, so a type must explicitly implement a trait using the impl Trait for Type syntax.
  • Traits are similar to interfaces in other languages, but they also allow you to provide default method implementations.
trait Person {
    fn name(&self) -> &str;
    fn age(&self) -> u32;
    
    fn greet(&self) {
        println!("Hello, my name is {} and I 'm {} years old", self.name(), self.age());
    }
}

struct Student {
    name: String,
    age: u32,
}

impl Person for Student {
    fn name(&self) -> &str {
        &self.name
    }
    
    fn age(&self) -> u32 {
        self.age
    }
}

fn main() {
    let student = Student { name: String::from("Alice"), age: 20 };
    student.greet();
}

In the provided example, name is of type String, which is a growable, heap-allocated string type. When you return a reference to the name field using &self.name, you are borrowing a reference to the String without transferring ownership. This allows the name field to remain owned by the original Student struct while still providing read access to its content. The return type of the name method is &str, an immutable string slice, so returning a reference to a String as &str is appropriate.

On the other hand, age is of type u32, which is a simple, fixed-size, and copyable integer type. When you return self.age, you are returning a copy of the age field because u32 implements the Copy trait. This means that returning self.age does not affect the ownership or borrowing of the age field. The return type of the age method is u32, which matches the type of the age field, so returning self.age directly is appropriate.

In summary, you use &self.name to return a reference to the String as an immutable string slice (&str), and you use self.age to return a copy of the u32 value.