[Rust] Concurrency trong Tokio?

Bài này chúng ta sẽ tìm hiểu về các hàm như join, stream race, spawn

Read more  ↩︎

[Blockchain] Solana Toàn Tập

Bài này chúng ta sẽ tìm hiểu về Solana . Mục đích là sau bài học này chúng ta có thể hiểu rõ tường minh những khái niệm của Solana mà cách để viết smart contract cũng như thao tác với mạng này

Read more  ↩︎

[Rust] Rust Lifetime

Bài viết này tìm hiểu về Rust Lifetime. Lifetime là một thuật ngữ không hẳn là mới nhưng chỉ đến ngôn ngữ Rust, nó được đưa thành cú pháp và được quan tâm đặc biệt. Viết code sử dụng generic với lifetime sẽ hơi khó đọc. Đây là một chủ đề khó. Bạn cũng không nên phiền lòng nếu như ngay lần đầu bạn không thể hiểu nó là gì. Nhiều người cũng như bạn thôi.

Lifetime là gì?

  • Rust cho phép 1 và chỉ 1 owner của một vùng nhớ
  • Rust cho phép nhiều tham chiếu
  • Lifetime thực thi phần của bộ nhớ là valid cho một reference

Điều này nghĩa là Rust đảm bảo cho tham chiếu luôn trỏ đến vùng nhớ hợp lệ, và lifetime đảm nhiệm tính năng này

Cú pháp lifetime

Sử dụng 'a, 'b, hoặc bất kỳ 'this_is_lifetime. Bạn có thể tuỳ ý đặc tên cho lifetime đó

Ngoài ra có một tham chiếu đặc biệt 'static. Nó báo hiệu lifetime này sẽ tồn tại đến hết chương trình. Các biến constant mặc định là dạng lifetime này

Nguyên tắc

  • Lifetime không áp dụng tới các hàm mà đầu vào và đầu ra không phải là tham chiếu. Ví dụ hàm fn test_1(param1: Vec<f64>)-> Vec<f64> không áp dụng. Hoặc nếu chỉ có đầu vào tham chiếu mà đầu ra không tham chiếu fn test_1(param1: &'a Vec<f64>)-> Vec<f64> thì cũng không áp dụng
  • Lifetime đảm bảo cho biến trả về có tham chiếu hợp lệ
  • Lifetime khai báo khi định nghĩa function, struct, impl
  • Những tham số nào không phải tham chiếu, compiler gán lifetime mặc định ok

Ví dụ 1: Tham chiếu đến một đối tượng bị huỷ


fn. main() {
    let a;
    {
        let b = String::from("Howdy");
        a = &b;
    }

   println!("{}",a);

}


Đoạn code trên gặp lỗi như sau:

error[E0597]: `b` does not live long enough
 --> src/lifetime_test.rs:5:13
  |
4 |         let b = String::from("Howdy");
  |             - binding `b` declared here
5 |         a = &b;
  |             ^^ borrowed value does not live long enough
6 |     }
  |     - `b` dropped here while still borrowed
7 |
8 |     println!("{}", a);
  |                    - borrow later used here

Giải thích: Bởi vì a thì trỏ đến b, mà khi sử dụng println!("{}",a); thì b đã bị drop đi sau khi ra khỏi {}

Ví dụ 2: Trả về một tham chiếu tới biến tạm hàm

fn get_int_ref() -> &i32 {
    let a = 1;
    &a
}

Thông tin lỗi

instead, you are more likely to want to return an owned valuerustcE0106
lifetime_test.rs(22, 21): original diagnostic
consider using the `'static` lifetime, but this is uncommon unless you're returning a borrowed value from a `const` or a `static`: `'static `rustcE0106
lifetime_test.rs(22, 21): original diagnostic
mismatched types
expected `&i32`, found `()`rustcClick for full compiler diagnostic
lifetime_test.rs(22, 4): implicitly returns `()` as its body has no tail or `return` expression
i32

Nó gợi ý cho chúng ta nên trả về 1 tham chiếu liftime dạng 'static sẽ giải quyết được vấn đề

Ví du 3: Nhận vào một tham chiếu và trả về tham chiếu

fn get_in_ref(param1 : &i32) -> &i32 {
    param1
}

fn main() {
    let variable = 10;
    let result = get_int_ref(&variable);
    println!("{}", result);
}

Chương trình này biến dịch được không có vấn đề gì

Ta có thể viết như sau bởi biến truyền vào với biến trả về cùng lifetime là trong giống {} của hàm main

fn get_in_ref<'a>(param1 : &'a i32) -> &'a i32 {
    param1
}

Ví dụ 4: Hàm với nhiều biến đầu vào tham chiếu

fn get_in_ref<'a>(param1 : &'a i32, param2 : &'a i32, param3 : &'a i32, param4 :i32) -> &'a i32 {
    param1
}

Trong turường hợp có nhiều đầu vào thì chúng ta cần xác định như sau:

  • Mỗi một đầu vào là tham chiếu sẽ có một có 1 lifetime riêng mà nó sở hữu và đặc tên lần lượt là 'a, 'b, 'c
fn get_in_ref<'a, 'b, 'c>(param1 : &'a i32, param2 : &'b i32, param3 : &'c i32, param4 :i32) -> &i32 {
    param1
}

Tham số thứ 4 không phải là reference nên không cần thêm chỉ thị lifetime.

Giờ bạn nhận thấy rằng tham số param2, param3 không bao giờ trả về

Đầu ra chúng ta thấy nó có cùng vòng đời với param1 nên ta có thể viết như sau

fn get_in_ref<'a, 'b, 'c>(param1 : &'a i32, param2 : &'b i32, param3 : &'c i32, param4 :i32) -> &'a i32 {
   param1
}

Biên dịch thành công!

Giả sử logic như này

fn get_in_ref<'a, 'b, 'c>(param1 : &'a i32, param2 : &'b i32, param3 : &'c i32, param4 :i32) -> &'a i32 {
   if param1 > param2 {
        param1
   } else {
        param2 // <-- may not live long enough
   }
}

Lúc đó thì chương trình sẽ báo lỗi

lifetime may not live long enough
consider adding the following bound: `'b: 'a`rustcClick for full compiler diagnostic
lifetime_test.rs(31, 19): lifetime `'b` defined here
lifetime_test.rs(31, 15): lifetime `'a` defined here
// size = 8, align = 0x8
param2: &i32

Hàm này bạn bảo rằng trả về một biến có vòng đời (lifetime) là 'a, nhưng tại sao trong code bạn trả về param2 có vòng đời là 'b Do dó compiler lo ngại rằng điều bạn muốn không được cam kết nên nó không compile và báo lỗi trên.

Compile cũng sẽ hỏi bạn một câu là thế thì 'b có liên quan gì đến 'a không. Nếu 'b > 'a thì tôi thấy okie đấy, còn 'b < 'a thì thôi không thấy ổn chút nào

Bạn có thể sửa như sau:'b: 'a đọc là 'b ít nhất có vòng đời là 'a

fn get_in_ref<'a, 'b: 'a, 'c>(param1 : &'a i32, param2 : &'b i32, param3 : &'c i32, param4 :i32) -> &'a i32 {
   if param1 > param2 {
        param1
   } else {
        param2 // <-- may not live long enough
   }
}

Biên dịch tốt!

Hoặc bạn nhận ra rằng param2 cũng có vòng đời giống param. Nghĩa là 'b = 'a nên thay thì viết như trên bạn thay 'b'a luôn xong cho nhanh.

fn main() {
    let variable1 = 10; // <--- Có cùng scope với varable2
    let variable2 = 20; // <--- Có cùng scope với variable1
    let variable3 = 30;
    let variable4 = 40;
    let result = get_int_ref(&variable1, &variable2, &variable3, variable4);
    println!("{}", result);
}

fn get_in_ref<'a, 'c>(
    param1: &'a i32,
    param2: &'a i32,
    param3: &'c i32,
    param4: i32,
) -> &'a i32 {
    if param1 > param2 {
        param1
    } else {
        param2
    }
}

Compiler vẫn happy và biên dịch tốt.

Ví du 5: Tham chiếu đầu ra, nhưng đầu vào không có tham chiếu

fn test4(param1: Vec<f64>) -> &Vec<f64> {
   &param1
}

Sẽ báo lỗi

...or alternatively, you might want to return an owned valuerustcE0106
lifetime_test.rs(52, 30): original diagnostic
consider using the `'static` lifetime, but this is uncommon unless you're returning a borrowed value from a `const` or a `static`: `'static `rustcE0106
lifetime_test.rs(52, 30): original diagnostic

Sửa lại như sau:

fn test4<'a>(param1: Vec<f64>) -> &'a Vec<f64> {
   &param1
}

Nhưng vẫn bị lỗi

cannot return reference to function parameter `param1`
returns a reference to data owned by the current functionrustcClick for full compiler diagnostic
// size = 24 (0x18), align = 0x8
param1: Vec<f64>

Ownership của param1 là được chuyển tới hàm này. Nó sẽ bị huỷ đi sau khi ra khỏi hàm. Trở về một tham chiếu bị drop sẽ không hợp lệ. Nên trong trường hợp hàm này chỉ có thể trả về một tham chiếu 'static hoặc đơn giản nhất là viết

fn test4(param1: Vec<f64>) -> Vec<f64> {
    param1
}

Vừa hợp lý vừa đúng.

Ví dụ: Lifetime với slice

fn get_vec_slice(param1: &[i32]) -> &[i32]{
    &param1[0..2]
}

Biên dịch ok và không vấn đề.

Ví dụ 7: Static lifetime

const A : &str = "Constant A";
const B: &str = "Constant B";
fn main() {
    let greater = some_func(&A, &B);
}

fn some_func(param1: &'static str, param2: &'static str) -> &'static str {
    if param1 > param2 {
        param1
    } else {
        param2
    }
}

Biên dịch ok và không vấn đề.

Nếu thay đổi thế này thì bị lỗi

const A : &str = "Constant A";
const B: &str = "Constant B";
fn main() {
    let a = String::from("a");
    let greater = some_func(&a, &B);
}

fn some_func(param1: &'static str, param2: &'static str) -> &'static str {
    if param1 > param2 {
        param1
    } else {
        param2
    }
}


`a` does not live long enough
borrowed value does not live long enoughrustcClick for full compiler diagnostic

Ví du 8: Lifetime với generic function

fn get_smaller<T: std::cmp::PartialOrd>(param1: &T, param2: &T) -> &T {
    if param1 < param2 {
        param1
    } else {
        param2
    }
}

Lỗi như sau:

missing lifetime specifier
this function's return type contains a borrowed value, but the signature does not say whether it is borrowed from `param1` or `param2`rustcClick for full compiler diagnostic

Sửa lại như sau:

fn get_smaller<'a, T: std::cmp::PartialOrd>(param1: &'a T, param2: &'a T) -> &'a T {
    if param1 < param2 {
        param1
    } else {
        param2
    }
}

Ví dụ 9: Lifetime trong struct

struct Mystruct<'a> {
    some_data: Vec<i32>,
    some_ref1: &'a Vec<i32>,
}
struct Mystruct<'a,'b> {
    some_data: Vec<i32>,
    some_ref1: &'a Vec<i32>,
    some_ref2: &'b Vec<i32>,
}