Hôm nay, chúng ta sẽ tìm hiểu một chút về lifetime elision trong Rust. Hiểu được luật của nó giúp chúng ta không bổi rối khi làm việc với Lifetime

ĐỊNH NGHĨA

Là một tính năng của Rust compiler mà giúp chúng ta biến đổi lifetime từ đầu vào tới đầu ra. Nói một cách khác, đây là một số rules giúp chúng ta biết cách khai báo ký hiệu lifetime 'a

KHI NÀO CHÚNG TA QUAN TÂM TỚI LIFETIME

Khi một hàm của chúng ta nhận vào danh sách các tham chiếu và cũng trả về tham chiếu thì chúng ta cần quan tâm đến nó

Sau đây là một ví dụ kinh điển

fn compare(x: &u32, y: &u32) -> &u32 {
	if x > y {
		x
	} else {
		y
	}
}

LUẬT

Có 3 rules mà chúng ta có thể bắt đầu.

  1. Mỗi một đầu vào dạng tham chiếu sẽ có 1 lifetime của chính nó.
  2. Nếu như đầu vào chỉ có 1 tham số là lifetime, nó mặc định gán lifetime tới tất cả các đầu ra
  3. Trong trường hợp có nhiều tham số đầu vào vào, một trong đầu vào là &self hoặc &mut self. Lifetime này sẽ được gán tới tất cả đầu ra

VÍ DỤ

Rule 1

fn update(&str, &str) -> &str

Trong hàm trên chúng ta có thể viết như sau theo luật 1

fn update<'a, 'b>(&'a str, &'bstr) -> &'str

Rule 2

fn update(&str) -> (&str, &str)

Theo luật 2 nó chỉ có một đầu vào nên mặc nhiên lifetime của đầu vào này sẽ là lifetime của đầu ra

fn  update(&'a str) -> (&'a str, &'a str)

Rule 3

fn update(&mut self, &str, i32) -> (&str, &str)

Trong trường hợp này ta có nhiều tham số đầu vào mà 1 trong các tham số đầu ra là &mut self. Chiếu theo luật 3 ta có thể viết

fn update<'a, 'b>(&'a mut self, &'b str, i32) -> (&'a str, &'a str)

Thằng &'b str nó có lifetime của riêng nó là 'b

LIFETIME VỚI STRUCT

struct ArrayProcessor {
	data: &'[i32],
}

Lifetime elision sẽ giúp ta có thể viết

struct ArrayProcessor<'a> {
	data: &'a [i32],
}

fn main(){
	let mut data = ArrayProcessor(data: &[1,2,3]);
}

Khai báo lifetime trong struct thể hiện biến data mượn có lifetime ít nhất phải bằng lifetime của struct chứa nó.

Viết thêm hàm impl impl ArrayProcessor {} compiler sẽ báo lỗi và yêu cầu chúng ta khai báo lifetime

struct ArrayProcessor<'a> {
	data: &'a [i32],
}
impl<'a> ArrayProcesssor<'a>{
	fn update(&mut self, new_data: &'a[i32]) -> &[i32] {
		let prev_data = self.data;
		self.data = new_data;
		prev_data
	}
}
fn main(){
	let mut data = ArrayProcessor(&[1,2,3]);
	let prev_data = data.update(&[4,5,6])
}

Chúng ta sẽ viết lại như sau theo 3 rules trên

impl<'a> ArrayProcesssor<'a>{
	fn update<'b>(&'b mut self, new_data: &'a [i32]) -> &'b [i32] {
		let prev_data = self.data;
		self.data = new_data;
		prev_data
	}
}

Have fun!