Tại sao không khuyến khích chấp nhận tham chiếu đến Chuỗi (& Chuỗi), Vec (& Vec) hoặc Hộp (& Box) làm đối số hàm?


127

Tôi đã viết một số mã Rust lấy một &Stringđối số làm đối số:

fn awesome_greeting(name: &String) {
    println!("Wow, you are awesome, {}!", name);
}

Tôi cũng đã viết mã có tham chiếu đến Vechoặc Box:

fn total_price(prices: &Vec<i32>) -> i32 {
    prices.iter().sum()
}

fn is_even(value: &Box<i32>) -> bool {
    **value % 2 == 0
}

Tuy nhiên, tôi nhận được một số phản hồi rằng làm như thế này không phải là một ý kiến ​​hay. Tại sao không?

Câu trả lời:


162

TL; DR: Thay vào đó &str, người ta có thể sử dụng &[T]hoặc &Tcho phép mã chung chung hơn.


  1. Một trong những lý do chính để sử dụng a Stringhoặc a Veclà vì chúng cho phép tăng hoặc giảm dung lượng. Tuy nhiên, khi bạn chấp nhận một tham chiếu không thay đổi, bạn không thể sử dụng bất kỳ phương pháp thú vị nào trên Vechoặc String.

  2. Việc chấp nhận a &String, &Vechoặc &Boxcũng yêu cầu đối số được cấp phát trên heap trước khi bạn có thể gọi hàm. Việc chấp nhận a &strcho phép một ký tự chuỗi (được lưu trong dữ liệu chương trình) và chấp nhận một &[T]hoặc &Tcho phép một mảng hoặc biến được phân bổ theo ngăn xếp. Phân bổ không cần thiết là một tổn thất hiệu suất. Điều này thường được hiển thị ngay lập tức khi bạn cố gắng gọi các phương thức này trong một bài kiểm tra hoặc một mainphương thức:

    awesome_greeting(&String::from("Anna"));
    total_price(&vec![42, 13, 1337])
    is_even(&Box::new(42))
  3. Một xem xét hiệu suất là &String, &Vec&Boxgiới thiệu một lớp không cần thiết về mình như bạn phải dereference sự &Stringđể có được một Stringvà sau đó thực hiện một dereference thứ hai để kết thúc tại &str.

Thay vào đó, bạn nên chấp nhận một chuỗi slice ( &str), một slice ( &[T]) hoặc chỉ một tham chiếu ( &T). A &String, &Vec<T>hoặc &Box<T>sẽ tự động bị ép buộc đối với a &str, &[T]hoặc &T, tương ứng.

fn awesome_greeting(name: &str) {
    println!("Wow, you are awesome, {}!", name);
}
fn total_price(prices: &[i32]) -> i32 {
    prices.iter().sum()
}
fn is_even(value: &i32) -> bool {
    *value % 2 == 0
}

Bây giờ bạn có thể gọi các phương thức này với nhiều loại kiểu hơn. Ví dụ, awesome_greetingcó thể được gọi với một chuỗi đen ( "Anna") hoặc một phân bổ String. total_pricecó thể được gọi với một tham chiếu đến một mảng ( &[1, 2, 3]) hoặc một phân bổ Vec.


Nếu bạn muốn thêm hoặc xóa các mục khỏi Stringhoặc Vec<T>, bạn có thể lấy tham chiếu có thể thay đổi ( &mut Stringhoặc &mut Vec<T>):

fn add_greeting_target(greeting: &mut String) {
    greeting.push_str("world!");
}
fn add_candy_prices(prices: &mut Vec<i32>) {
    prices.push(5);
    prices.push(25);
}

Cụ thể cho các lát, bạn cũng có thể chấp nhận một &mut [T]hoặc &mut str. Điều này cho phép bạn thay đổi một giá trị cụ thể bên trong lát, nhưng bạn không thể thay đổi số lượng mục bên trong lát (có nghĩa là nó rất hạn chế đối với chuỗi):

fn reset_first_price(prices: &mut [i32]) {
    prices[0] = 0;
}
fn lowercase_first_ascii_character(s: &mut str) {
    if let Some(f) = s.get_mut(0..1) {
        f.make_ascii_lowercase();
    }
}

5
Làm thế nào về một tl; dr trong đầu? Câu trả lời này đã hơi dài. Một cái gì đó như " &strlà tổng quát hơn (như trong: áp đặt ít hạn chế hơn) mà không giảm khả năng"? Ngoài ra: tôi nghĩ điểm 3 thường không quan trọng lắm. Thông thường Vecs và Strings sẽ sống trên ngăn xếp và thường thậm chí ở đâu đó gần khung ngăn xếp hiện tại. Ngăn xếp thường nóng và tham chiếu sẽ được phân phát từ bộ đệm CPU.
Lukas Kalbertodt

3
@Shepmaster: Về chi phí phân bổ, có thể cần đề cập đến vấn đề cụ thể của chuỗi con / lát khi nói về phân bổ bắt buộc. total_price(&prices[0..4])không yêu cầu cấp phát một vectơ mới cho lát cắt.
Matthieu M.

4
Đây là một câu trả lời tuyệt vời. Tôi chỉ mới bắt đầu với Rust và đang bị ràng buộc phải tìm ra khi nào tôi nên sử dụng một &strtại sao (đến từ Python, vì vậy tôi thường không giải quyết rõ ràng các loại). Xóa tất cả điều đó lên một cách hoàn hảo
C.Nivs

2
Mẹo tuyệt vời về các thông số. Chỉ cần một nghi ngờ: "Việc chấp nhận một & String, & Vec hoặc & Box cũng yêu cầu một cấp phát trước khi bạn có thể gọi phương thức." ... Tại sao lại như vậy? Bạn có thể vui lòng chỉ ra phần trong tài liệu mà tôi có thể đọc chi tiết phần này không? (Tôi là một người ăn xin). Ngoài ra, chúng ta có thể có các mẹo tương tự về các loại trả lại không?
Nawaz

2
Tôi thiếu thông tin về lý do tại sao cần phải phân bổ thêm. Chuỗi được lưu trữ trên heap, khi chấp nhận & String làm đối số, tại sao Rust không chỉ truyền một con trỏ được lưu trữ trên ngăn xếp mà trỏ đến không gian heap, tôi không hiểu tại sao việc truyền & String lại cần phân bổ bổ sung, truyền một chuỗi lát cắt cũng yêu cầu gửi một con trỏ được lưu trữ trên ngăn xếp trỏ đến không gian đống?
cjohansson

22

Ngoài câu trả lời của Shepmaster , một lý do khác để chấp nhận a &str(và tương tự, &[T]v.v.) là vì tất cả các loại khác bên cạnh String&strđiều đó cũng đáp ứng Deref<Target = str>. Một trong những ví dụ đáng chú ý nhất là Cow<str>, điều này cho phép bạn rất linh hoạt về việc bạn đang xử lý dữ liệu được sở hữu hay mượn.

Nếu bạn có:

fn awesome_greeting(name: &String) {
    println!("Wow, you are awesome, {}!", name);
}

Nhưng bạn cần phải gọi nó bằng a Cow<str>, bạn sẽ phải làm điều này:

let c: Cow<str> = Cow::from("hello");
// Allocate an owned String from a str reference and then makes a reference to it anyway!
awesome_greeting(&c.to_string());

Khi bạn thay đổi loại đối số thành &str, bạn có thể sử dụng Cowliền mạch mà không cần phân bổ không cần thiết, giống như với String:

let c: Cow<str> = Cow::from("hello");
// Just pass the same reference along
awesome_greeting(&c);

let c: Cow<str> = Cow::from(String::from("hello"));
// Pass a reference to the owned string that you already have
awesome_greeting(&c);

Việc chấp nhận &strlàm cho việc gọi hàm của bạn đồng nhất và thuận tiện hơn, và cách "dễ nhất" bây giờ cũng là cách hiệu quả nhất. Những ví dụ này cũng sẽ hoạt động với Cow<[T]>v.v.

Khi sử dụng trang web của chúng tôi, bạn xác nhận rằng bạn đã đọc và hiểu Chính sách cookieChính sách bảo mật của chúng tôi.
Licensed under cc by-sa 3.0 with attribution required.