Sự khác biệt giữa `String` và` str` của Rust là gì?


420

Tại sao Rust có Stringstr? Sự khác biệt giữa Stringvà là strgì? Khi nào một người sử dụng Stringthay vì strvà ngược lại? Là một trong số họ bị phản đối?

Câu trả lời:


491

Stringlà loại chuỗi heap động, như Vec: sử dụng nó khi bạn cần sở hữu hoặc sửa đổi dữ liệu chuỗi của mình.

strlà một chuỗi bất biến 1 chuỗi UTF-8 byte có độ dài động ở đâu đó trong bộ nhớ. Vì kích thước không xác định, người ta chỉ có thể xử lý nó phía sau một con trỏ. Điều này có nghĩa là strphổ biến nhất 2 xuất hiện dưới dạng &str: tham chiếu đến một số dữ liệu UTF-8, thường được gọi là "chuỗi cắt" hoặc chỉ là "lát". Một lát chỉ là một cái nhìn vào một số dữ liệu và dữ liệu đó có thể ở bất cứ đâu, ví dụ

  • Trong lưu trữ tĩnh : một chuỗi ký tự "foo"là a &'static str. Dữ liệu được mã hóa cứng vào tệp thực thi và được tải vào bộ nhớ khi chương trình chạy.
  • Bên trong một đống phân bổString : Stringdereferences đến một &strcái nhìn của Stringdữ liệu 's.
  • Trên ngăn xếp : ví dụ sau đây tạo ra một mảng byte được cấp phát ngăn xếp, và sau đó có được chế độ xem dữ liệu đó dưới dạng&str :

    use std::str;
    
    let x: &[u8] = &[b'a', b'b', b'c'];
    let stack_str: &str = str::from_utf8(x).unwrap();

Tóm lại, sử dụng Stringnếu bạn cần dữ liệu chuỗi sở hữu (như truyền chuỗi cho các luồng khác hoặc xây dựng chúng khi chạy) và sử dụng &strnếu bạn chỉ cần chế độ xem chuỗi.

Điều này giống hệt với mối quan hệ giữa một vectơ Vec<T>và một lát cắt &[T], và tương tự như mối quan hệ giữa giá trị phụ Tvà tham chiếu phụ &Tcho các loại chung.


1 A strlà chiều dài cố định; bạn không thể ghi các byte vượt quá cuối hoặc để lại các byte không hợp lệ. Vì UTF-8 là một mã hóa có chiều rộng thay đổi, điều này có hiệu quả buộc tất cả các strs phải bất biến trong nhiều trường hợp. Nói chung, đột biến yêu cầu ghi nhiều hoặc ít byte hơn so với trước đây (ví dụ: thay thế a(1 byte) bằng ä(2+ byte) sẽ yêu cầu tạo nhiều chỗ hơn trong str). Có các phương thức cụ thể có thể sửa đổi &strtại chỗ, chủ yếu là các phương thức chỉ xử lý các ký tự ASCII, như thế nào make_ascii_uppercase.

2 Các loại có kích thước động cho phép những thứ như Rc<str>cho một chuỗi tham chiếu UTF-8 byte kể từ Rust 1.2. Rust 1.21 cho phép dễ dàng tạo ra các loại này.


10
"chuỗi các UTF-8 byte ( có độ dài không xác định )" - đây có phải là lỗi thời không? Các tài liệu nói rằng "A &strđược tạo thành từ hai thành phần: một con trỏ tới một số byte và chiều dài."
mrec

11
Nó không lỗi thời (đại diện đó đã khá ổn định), chỉ là một chút thiếu chính xác: nó không được biết đến một cách tĩnh, không giống như, nói , [u8; N].
huon

2
@mrec Không biết tại thời điểm biên dịch, các giả định về kích thước của nó không thể được thực hiện, ví dụ như khi tạo khung ngăn xếp. Do đó, tại sao nó thường được coi là một tham chiếu, mà tham chiếu là kích thước đã biết tại thời điểm biên dịch, là kích thước của một con trỏ.
Sekhat

1
Cập nhật: Rc<str>Arc<str>hiện có thể sử dụng thông qua thư viện tiêu chuẩn.
Centril

1
@cjohansson Các đối tượng được phân bổ tĩnh thường được lưu trữ không phải trên heap, cũng không phải ngăn xếp, nhưng trong vùng bộ nhớ riêng của chúng.
Brennan Vincent

97

Tôi có một nền tảng C ++ và tôi thấy nó rất hữu ích để suy nghĩ String&strvề các thuật ngữ C ++:

  • Một Rust Stringgiống như một std::string; nó sở hữu bộ nhớ và thực hiện công việc bẩn thỉu là quản lý bộ nhớ.
  • Một Rust &strgiống như một char*(nhưng tinh vi hơn một chút); nó chỉ cho chúng ta bắt đầu một đoạn giống như cách bạn có thể lấy một con trỏ tới nội dung của std::string.

Là một trong số họ sẽ biến mất? Tôi không nghĩ vậy. Họ phục vụ hai mục đích:

Stringgiữ bộ đệm và rất thiết thực để sử dụng. &strlà trọng lượng nhẹ và nên được sử dụng để "nhìn" vào chuỗi. Bạn có thể tìm kiếm, phân tách, phân tích cú pháp và thậm chí thay thế các đoạn mà không cần phân bổ bộ nhớ mới.

&strcó thể nhìn vào bên trong Stringvì nó có thể trỏ đến một chuỗi ký tự. Đoạn mã sau cần sao chép chuỗi ký tự vào Stringbộ nhớ được quản lý:

let a: String = "hello rust".into();

Đoạn mã sau cho phép bạn sử dụng chính chữ mà không cần sao chép (chỉ đọc)

let a: &str = "hello rust";

12
như một chuỗi_view?
Abhinav Gauniyal

1
Có như string_view nhưng thực chất với ngôn ngữ và được kiểm tra mượn đúng cách.
locka

41

str, chỉ được sử dụng như &strlà một lát cắt chuỗi, tham chiếu đến mảng byte UTF-8.

Stringlà những gì từng là ~str, một mảng byte UTF-8 có thể phát triển được.


Về mặt kỹ thuật, những gì trước đây ~strlà bây giờBox<str>
jv110

3
@ jv110: không, vì ~strđã phát triển được trong khi Box<str>không thể phát triển được. (Đó ~str~[T]là kỳ diệu growable, không giống như bất kỳ khác ~-object, chính xác là lý do tại sao StringVec<T>đã được giới thiệu, do đó các quy tắc đều đơn giản và nhất quán.)
Chris Morgan

18

Họ thực sự hoàn toàn khác nhau. Trước hết, a strkhông có gì ngoài một loại cấp độ; nó chỉ có thể được lý luận ở cấp độ loại vì nó được gọi là loại kích thước động (DST). Kích thước strchiếm không thể được biết tại thời gian biên dịch và phụ thuộc vào thông tin thời gian chạy - nó không thể được lưu trữ trong một biến vì trình biên dịch cần biết tại thời điểm biên dịch kích thước của mỗi biến là gì. A strvề mặt khái niệm chỉ là một hàng u8byte với sự đảm bảo rằng nó tạo thành UTF-8 hợp lệ. Hàng rộng bao nhiêu? Không ai biết cho đến khi thời gian chạy do đó nó không thể được lưu trữ trong một biến.

Điều thú vị là một &strhoặc bất kỳ con trỏ khác để một strnhư Box<str> không tồn tại trong thời gian chạy. Đây là cái gọi là "con trỏ béo"; đó là một con trỏ có thêm thông tin (trong trường hợp này là kích thước của vật đang chỉ vào) nên nó lớn gấp đôi. Trong thực tế, a &strkhá gần với a String(nhưng không phải với a &String). A &strlà hai từ; một con trỏ tới byte đầu tiên của a strvà một số khác mô tả số byte dài bao nhiêu str.

Trái với những gì được nói, a strkhông cần phải bất biến. Nếu bạn có thể lấy một &mut strcon trỏ độc quyền cho str, bạn có thể biến đổi nó và tất cả các hàm an toàn biến đổi nó đảm bảo rằng ràng buộc UTF-8 được giữ nguyên vì nếu vi phạm thì chúng ta có hành vi không xác định vì thư viện giả định ràng buộc này là đúng và không kiểm tra nó

Vậy a là Stringgì? Đó là ba từ; hai là giống nhau cho &strnhưng nó thêm một từ thứ ba là dung lượng của strbộ đệm trên heap, luôn luôn trên heap ( strkhông nhất thiết phải trên heap) mà nó quản lý trước khi nó được lấp đầy và phải phân bổ lại. các Stringcơ bản sở hữu một strnhư họ nói; nó kiểm soát nó và có thể thay đổi kích thước và phân bổ lại khi thấy phù hợp. Vì vậy, a Stringđược nói gần với a &strhơn là a str.

Một điều nữa là Box<str>; cái này cũng sở hữu một strvà đại diện thời gian chạy của nó giống như một &strnhưng nó cũng sở hữu strkhông giống như &strnhưng nó không thể thay đổi kích thước vì nó không biết khả năng của nó nên về cơ bản Box<str>có thể được xem là độ dài cố định Stringkhông thể thay đổi kích thước (bạn có thể thay đổi kích thước luôn luôn chuyển đổi nó thành một Stringnếu bạn muốn thay đổi kích thước nó).

Một mối quan hệ rất giống nhau tồn tại giữa [T]Vec<T>ngoại trừ không có ràng buộc UTF-8 và nó có thể giữ bất kỳ loại nào có kích thước không động.

Việc sử dụng strở mức độ chủ yếu là để tạo ra sự trừu tượng chung chung với &str; nó tồn tại ở cấp độ loại để có thể viết các đặc điểm một cách thuận tiện. Về lý thuyết strnhư là một loại điều không cần phải tồn tại và chỉ &strnhưng điều đó có nghĩa là rất nhiều mã bổ sung sẽ phải được viết mà bây giờ có thể là chung chung.

&strlà siêu hữu ích để có thể có nhiều chuỗi con khác nhau Stringmà không cần phải sao chép; như đã nói một String sở hữu các strtrên heap nó quản lý và nếu bạn chỉ có thể tạo ra một chuỗi con của một Stringvới một mới Stringnó sẽ phải sao chép bởi vì tất cả mọi thứ trong Rust chỉ có thể có một chủ sở hữu duy nhất để đối phó với an toàn bộ nhớ. Vì vậy, ví dụ bạn có thể cắt một chuỗi:

let string: String   = "a string".to_string();
let substring1: &str = &string[1..3];
let substring2: &str = &string[2..4];

Chúng tôi có hai chuỗi con khác nhau strcủa cùng một chuỗi. stringlà cái sở hữu strbộ đệm đầy đủ thực tế trên heap và các &strchuỗi con chỉ là các con trỏ chất béo cho bộ đệm đó trên heap.


4

std::Stringchỉ đơn giản là một vectơ của u8. Bạn có thể tìm thấy định nghĩa của nó trong mã nguồn . Đó là phân bổ đống và có thể phát triển.

#[derive(PartialOrd, Eq, Ord)]
#[stable(feature = "rust1", since = "1.0.0")]
pub struct String {
    vec: Vec<u8>,
}

strlà một kiểu nguyên thủy, còn được gọi là chuỗi lát . Một lát cắt chuỗi có kích thước cố định. Một chuỗi ký tự như let test = "hello world"&'static strloại. testlà một tham chiếu đến chuỗi phân bổ tĩnh này. &strkhông thể sửa đổi, ví dụ,

let mut word = "hello world";
word[0] = 's';
word.push('\n');

strkhông có lát cắt đột biến &mut str, ví dụ: pub fn split_at_mut(&mut self, mid: usize) -> (&mut str, &mut str)

let mut s = "Per Martin-Löf".to_string();
{
    let (first, last) = s.split_at_mut(3);
    first.make_ascii_uppercase();
    assert_eq!("PER", first);
    assert_eq!(" Martin-Löf", last);
}
assert_eq!("PER Martin-Löf", s);

Nhưng một thay đổi nhỏ đối với UTF-8 có thể thay đổi độ dài byte của nó và một lát cắt không thể phân bổ lại tham chiếu của nó.


0

Nói một cách dễ hiểu, Stringkiểu dữ liệu được lưu trữ trên heap (giống như Vec) và bạn có quyền truy cập vào vị trí đó.

&strlà một loại lát. Điều đó có nghĩa là nó chỉ là tham chiếu đến một thứ đã có mặt Stringở đâu đó trong đống.

&strkhông thực hiện bất kỳ phân bổ nào trong thời gian chạy. Vì vậy, vì lý do bộ nhớ, bạn có thể sử dụng &strhơn String. Nhưng, hãy nhớ rằng khi sử dụng &strbạn có thể phải đối phó với tuổi thọ rõ ràng.


1
một nơi nào đó trong đống - điều đó không hoàn toàn chính xác.
Người quản lý

Những gì tôi có nghĩa là strviewcủa đã hiện diện Stringtrong heap.
00imvj00

1
Tôi hiểu đó là những gì bạn muốn nói và tôi đang nói điều đó không hoàn toàn chính xác. "Heap" không phải là một phần bắt buộc của tuyên bố.
Người quản lý

-1

Dành cho người C # và Java:

  • Rỉ ' String===StringBuilder
  • &str Chuỗi của Rust === (không thay đổi)

Tôi muốn nghĩ về một &strchế độ xem trên một chuỗi, giống như một chuỗi được thực hiện trong Java / C # nơi bạn không thể thay đổi nó, chỉ tạo một chuỗi mới.


1
Sự khác biệt lớn nhất giữa các chuỗi Java / C # và các chuỗi Rust là Rust đảm bảo chuỗi đó là unicode chính xác, vì vậy để có được bộ ký tự thứ ba trong một chuỗi đòi hỏi nhiều suy nghĩ hơn là chỉ "abc" [2]. (Cho chúng ta sống trong một thế giới đa ngôn ngữ, đây là một điều tốt.)
Squirrel

Điều này là không chính xác . Chủ đề về tính đột biến đã được đề cập trong câu trả lời được bình chọn hàng đầu; xin vui lòng đọc nó để tìm hiểu thêm.
Người quản lý

-5

Đây là một lời giải thích nhanh chóng và dễ dàng.

String- Một cấu trúc dữ liệu phân bổ heap có thể phát triển được. Nó có thể bị ép buộc đến a &str.

str- là (bây giờ, khi Rust tiến hóa) chuỗi có thể thay đổi, có độ dài cố định sống trên heap hoặc trong nhị phân. Bạn chỉ có thể tương tác với strloại đã mượn thông qua chế độ xem chuỗi, chẳng hạn như &str.

Cân nhắc sử dụng:

Thích hơn Stringnếu bạn muốn sở hữu hoặc thay đổi một chuỗi - chẳng hạn như chuyển chuỗi này sang chuỗi khác, v.v.

Thích &strnếu bạn muốn có chế độ xem chỉ đọc của chuỗi.


Điều này là không chính xác . Chủ đề về tính đột biến đã được đề cập trong câu trả lời được bình chọn hàng đầu; xin vui lòng đọc nó để tìm hiểu thêm.
Người quản lý
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.