Tại sao Rust có String
và str
? Sự khác biệt giữa String
và là str
gì? Khi nào một người sử dụng String
thay vì str
và ngược lại? Là một trong số họ bị phản đối?
Tại sao Rust có String
và str
? Sự khác biệt giữa String
và là str
gì? Khi nào một người sử dụng String
thay vì str
và ngược lại? Là một trong số họ bị phản đối?
Câu trả lời:
String
là 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.
str
là 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à str
phổ 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ụ
"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.String
: String
dereferences đến một &str
cái nhìn của String
dữ 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 String
nế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 &str
nế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ụ T
và tham chiếu phụ &T
cho các loại chung.
1 A str
là 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 str
s 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 &str
tạ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.
[u8; N]
.
Rc<str>
và Arc<str>
hiện có thể sử dụng thông qua thư viện tiêu chuẩn.
Tôi có một nền tảng C ++ và tôi thấy nó rất hữu ích để suy nghĩ String
và &str
về các thuật ngữ C ++:
String
giố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ớ.&str
giố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:
String
giữ bộ đệm và rất thiết thực để sử dụng. &str
là 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.
&str
có thể nhìn vào bên trong String
vì 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 String
bộ 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";
str
, chỉ được sử dụng như &str
là một lát cắt chuỗi, tham chiếu đến mảng byte UTF-8.
String
là những gì từng là ~str
, một mảng byte UTF-8 có thể phát triển được.
~str
là bây giờBox<str>
~str
đã phát triển được trong khi Box<str>
không thể phát triển được. (Đó ~str
và ~[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 String
và Vec<T>
đã được giới thiệu, do đó các quy tắc đều đơn giản và nhất quán.)
Họ thực sự hoàn toàn khác nhau. Trước hết, a str
khô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 str
chiế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 str
về mặt khái niệm chỉ là một hàng u8
byte 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 &str
hoặc bất kỳ con trỏ khác để một str
như 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 &str
khá gần với a String
(nhưng không phải với a &String
). A &str
là hai từ; một con trỏ tới byte đầu tiên của a str
và 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 str
không cần phải bất biến. Nếu bạn có thể lấy một &mut str
con 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à String
gì? Đó là ba từ; hai là giống nhau cho &str
nhưng nó thêm một từ thứ ba là dung lượng của str
bộ đệm trên heap, luôn luôn trên heap ( str
khô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 String
cơ bản sở hữu một str
như 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 &str
hơn là a str
.
Một điều nữa là Box<str>
; cái này cũng sở hữu một str
và đại diện thời gian chạy của nó giống như một &str
nhưng nó cũng sở hữu str
không giống như &str
như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 String
khô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 String
nế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]
và 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 str
như là một loại điều không cần phải tồn tại và chỉ &str
như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.
&str
là siêu hữu ích để có thể có nhiều chuỗi con khác nhau String
mà không cần phải sao chép; như đã nói một String
sở hữu các str
trê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 String
với một mới String
nó 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 str
của cùng một chuỗi. string
là cái sở hữu str
bộ đệm đầy đủ thực tế trên heap và các &str
chuỗi con chỉ là các con trỏ chất béo cho bộ đệm đó trên heap.
std::String
chỉ đơ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>,
}
str
là 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"
có &'static str
loại. test
là một tham chiếu đến chuỗi phân bổ tĩnh này.
&str
không thể sửa đổi, ví dụ,
let mut word = "hello world";
word[0] = 's';
word.push('\n');
str
khô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ó.
Nói một cách dễ hiểu, String
kiể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í đó.
&str
là 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.
&str
khô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 &str
hơn String
. Nhưng, hãy nhớ rằng khi sử dụng &str
bạn có thể phải đối phó với tuổi thọ rõ ràng.
str
là view
của đã hiện diện String
trong heap.
Dành cho người C # và Java:
String
===StringBuilder
&str
Chuỗi của Rust === (không thay đổi)Tôi muốn nghĩ về một &str
chế độ 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.
Đâ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 str
loạ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 String
nế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 &str
nếu bạn muốn có chế độ xem chỉ đọc của chuỗi.
&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."