อะไรคือความแตกต่างระหว่าง "String" และ "str" ​​ของ Rust


418

ทำไมสนิมถึงมีStringและstr? อะไรคือความแตกต่างระหว่างStringและstr? เมื่อไหร่จะใช้Stringแทนstrและในทางกลับกัน? เป็นหนึ่งในพวกเขาได้รับการคัดค้าน?

คำตอบ:


489

Stringเป็นสตริงสตริงฮีปแบบไดนามิกเช่นVec: ใช้เมื่อคุณต้องการเป็นเจ้าของหรือแก้ไขข้อมูลสตริงของคุณ

strเป็นไม่เปลี่ยนรูป1ลำดับของ UTF-8 ไบต์ของความยาวอยู่ที่ไหนสักแห่งในหน่วยความจำแบบไดนามิก เนื่องจากขนาดไม่เป็นที่รู้จักจึงสามารถจัดการกับด้านหลังตัวชี้ได้เท่านั้น ซึ่งหมายความว่าstrโดยทั่วไป2จะปรากฏเป็น&str: การอ้างอิงถึงข้อมูล UTF-8 บางอย่างซึ่งโดยปกติจะเรียกว่า "string slice" หรือเพียงแค่ "slice" ชิ้นงานเป็นเพียงการดูข้อมูลบางส่วนและข้อมูลนั้นสามารถอยู่ที่ใดก็ได้เช่น

  • ในการจัดเก็บข้อมูลแบบคงที่ : ตัวอักษรสตริงเป็น"foo" &'static strข้อมูลจะถูกเข้ารหัสลงในไฟล์เรียกทำงานและโหลดลงในหน่วยความจำเมื่อโปรแกรมทำงาน
  • ภายในกองที่จัดสรรString : Stringการ&strยกเลิกการกำหนดมุมมองของStringข้อมูล
  • ในสแต็ก : เช่นต่อไปนี้สร้างอาร์เรย์ไบต์ที่จัดสรรสแต็กแล้วรับมุมมองของข้อมูลนั้นเป็น&str :

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

โดยสรุปให้ใช้Stringหากคุณต้องการข้อมูลสตริงที่เป็นเจ้าของ (เช่นการส่งสตริงไปยังเธรดอื่น ๆ หรือสร้างพวกมันตอนรันไทม์) และใช้&strถ้าคุณต้องการมุมมองของสตริงเท่านั้น

สิ่งนี้เหมือนกับความสัมพันธ์ระหว่างเวกเตอร์Vec<T>กับชิ้น&[T]หนึ่งและมีความคล้ายคลึงกับความสัมพันธ์ระหว่างค่าTและการอ้างอิง&Tสำหรับชนิดทั่วไป


1 A strมีความยาวคงที่ คุณไม่สามารถเขียนไบต์เกินกว่าสิ้นสุดหรือปล่อยไบต์ที่ไม่ถูกต้องต่อท้าย เนื่องจาก UTF-8 เป็นการเข้ารหัสความกว้างแปรผันการบังคับให้strs ทั้งหมดนั้นไม่เปลี่ยนรูปในหลาย ๆ กรณี โดยทั่วไปการกลายพันธุ์จะต้องเขียนมากขึ้นหรือน้อยลงกว่าที่เคยมีมาก่อน (เช่นการแทนที่a(1 ไบต์) ด้วยä(2+ ไบต์) จะต้องทำให้มีที่ว่างมากขึ้นในstr) มีวิธีการเฉพาะที่สามารถปรับเปลี่ยนให้&strอยู่ในสถานที่ส่วนใหญ่เป็นผู้ที่จับเฉพาะอักขระ ASCII make_ascii_uppercaseเหมือน

2 ประเภทที่มีขนาดแบบไดนามิกช่วยให้สิ่งต่าง ๆ เช่นRc<str>ลำดับของการอ้างอิงนับ UTF-8 นับตั้งแต่ Rust 1.2 Rust 1.21 ช่วยให้สามารถสร้างประเภทเหล่านี้ได้อย่างง่ายดาย


10
"ลำดับ UTF-8 ไบต์ ( ความยาวที่ไม่รู้จัก )" - มันล้าสมัยหรือไม่? เอกสารกล่าวว่า " &strถูกสร้างขึ้นจากสององค์ประกอบตัวชี้ไปยังไบต์บางส่วนและความยาว."
mrec

11
มันไม่ได้มาจากวันที่ (ตัวแทนที่ได้รับเสถียรธรรม) เพียงไม่แน่ชัดเล็ก ๆ น้อย ๆ [u8; N]มันไม่เป็นที่รู้จักแบบคงที่ซึ่งแตกต่างจากการพูด
huon

2
@mrec ไม่ทราบเวลาที่รวบรวมข้อสมมติฐานเกี่ยวกับขนาดไม่สามารถทำได้ตัวอย่างเช่นเมื่อสร้างเฟรมสแต็ก ดังนั้นทำไมจึงมักจะถือว่าเป็นข้อมูลอ้างอิงซึ่งการอ้างอิงเป็นขนาดที่รู้จัก ณ เวลารวบรวมซึ่งเป็นขนาดของตัวชี้
Sekhat

1
อัปเดต: Rc<str>และArc<str>ตอนนี้สามารถใช้งานได้ผ่านไลบรารีมาตรฐาน
Centril

1
@cjohansson วัตถุที่จัดสรรแบบคงที่จะถูกจัดเก็บตามปกติไม่ว่าจะเป็นบนกองหรือกอง แต่ในพื้นที่หน่วยความจำของตัวเอง
เบรนแนนวินเซนต์

96

ฉันมีพื้นหลัง C ++ และฉันคิดว่ามันมีประโยชน์มากที่จะคิดStringและเป็น&strศัพท์ C ++:

  • สนิมStringเป็นเหมือนstd::string; มันเป็นเจ้าของหน่วยความจำและทำงานสกปรกในการจัดการหน่วยความจำ
  • สนิม&strเป็นเหมือนchar*(แต่มีความซับซ้อนมากกว่า); std::stringมันชี้ให้เราสามารถเริ่มต้นของก้อนในลักษณะเดียวกับที่คุณจะได้รับตัวชี้ไปยังเนื้อหาของ

ทั้งคู่จะหายไปหรือไม่? ฉันไม่คิดเช่นนั้น. พวกเขาให้บริการสองวัตถุประสงค์:

Stringเก็บบัฟเฟอร์และใช้งานได้จริงมาก &strมีน้ำหนักเบาและควรใช้เพื่อ "ค้นหา" ลงในสตริง คุณสามารถค้นหาแยกวิเคราะห์และแม้แต่แทนที่ชิ้นโดยไม่ต้องจัดสรรหน่วยความจำใหม่

&strสามารถดูด้านในของ a Stringตามที่มันสามารถชี้ไปที่สตริงตัวอักษรบางอย่าง รหัสต่อไปนี้จำเป็นต้องคัดลอกสตริงตัวอักษรลงในStringหน่วยความจำที่มีการจัดการ:

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

รหัสต่อไปนี้ช่วยให้คุณใช้ตัวอักษรโดยไม่ต้องคัดลอก (อ่านเท่านั้น)

let a: &str = "hello rust";

12
ชอบ string_view ไหม?
Abhinav Gauniyal

1
ใช่ชอบ string_view แต่อยู่ในตัวภาษาและตรวจสอบการยืมอย่างถูกต้อง
locka

41

strใช้เป็นเพียงเป็น&strชิ้นส่วนอ้างอิงถึงอาร์เรย์ UTF-8

Stringเป็นสิ่งที่เคยเป็น~strอาร์เรย์ไบต์ UTF-8 ที่เติบโตได้และเป็นเจ้าของ


ในทางเทคนิคสิ่งที่เคย~strเป็นตอนนี้Box<str>
jv110

3
@ jv110: ไม่เพราะสามารถ~strเติบโตได้ในขณะที่Box<str>ไม่สามารถเติบโตได้ (นั่น~strและเติบโต~[T]ได้อย่างน่าอัศจรรย์ไม่เหมือน~-object อื่น ๆคืออะไรทำไมStringและVec<T>ถูกนำมาใช้เพื่อให้กฎนั้นตรงไปตรงมาและสอดคล้องกัน)
Chris Morgan

18

พวกเขาแตกต่างอย่างสิ้นเชิงจริง ๆ ก่อนอื่น a strคืออะไร แต่เป็นสิ่งที่ระดับประเภท; สามารถให้เหตุผลได้เฉพาะที่ระดับประเภทเท่านั้นเนื่องจากเป็นประเภทที่เรียกว่าแบบไดนามิก (DST) ขนาดที่strใช้ในการรวบรวมไม่สามารถทราบได้ในเวลารวบรวมและขึ้นอยู่กับข้อมูลรันไทม์ - ไม่สามารถเก็บไว้ในตัวแปรได้เนื่องจากคอมไพเลอร์จำเป็นต้องรู้ ณ เวลารวบรวมซึ่งขนาดของแต่ละตัวแปรคืออะไร A strเป็นแนวคิดเพียงแถวของu8ไบต์ด้วยการรับประกันว่ามันจะเป็นรูปแบบ UTF-8 ที่ถูกต้อง แถวใหญ่แค่ไหน ไม่มีใครรู้จนกระทั่งรันไทม์ดังนั้นจึงไม่สามารถเก็บไว้ในตัวแปร

สิ่งที่น่าสนใจก็คือว่า&strหรือตัวชี้อื่น ๆ ไปstrเหมือนBox<str> จะมีอยู่ที่รันไทม์ นี่คือสิ่งที่เรียกว่า "ตัวชี้ไขมัน"; มันเป็นตัวชี้ที่มีข้อมูลเพิ่มเติม (ในกรณีนี้ขนาดของสิ่งที่ชี้ไป) ดังนั้นมันจึงใหญ่เป็นสองเท่า ในความเป็นจริงแล้ว a &strค่อนข้างใกล้กับ a String(แต่ไม่ใช่ a &String) A &strคือคำสองคำ หนึ่งในตัวชี้ไปยังไบต์แรกของstrและจำนวนที่อธิบายอีกว่าหลายไบต์ยาวstrคือ

ตรงกันข้ามกับสิ่งที่ถูกกล่าวว่า a strไม่จำเป็นต้องไม่เปลี่ยนรูป หากคุณสามารถ&mut strเป็นตัวชี้พิเศษให้กับstrคุณสามารถกลายพันธุ์ได้และฟังก์ชั่นที่ปลอดภัยทั้งหมดที่กลายพันธุ์นั้นรับประกันได้ว่าข้อ จำกัด UTF-8 นั้นจะได้รับการรักษาเพราะถ้าหากมีการละเมิดเราจะมีพฤติกรรมที่ไม่ได้กำหนดไว้ จริงและไม่ได้ตรวจสอบ

ดังนั้นคือStringอะไร นั่นคือสามคำ สองเหมือนกัน&strแต่จะเพิ่มคำที่สามซึ่งเป็นความจุของstrบัฟเฟอร์บนฮีปเสมอบนฮีป (a strไม่จำเป็นต้องอยู่บนฮีป) จะจัดการก่อนที่จะเต็มและต้องจัดสรรใหม่ Stringพื้นเป็นเจ้าของstrที่เขาเรียกว่า มันควบคุมและสามารถปรับขนาดและจัดสรรใหม่เมื่อเห็นว่าเหมาะสม ดังนั้นStringจะเป็นกล่าวใกล้ชิดกับกว่าไป&strstr

อีกสิ่งหนึ่งคือBox<str>; สิ่งนี้ยังเป็นเจ้าของstrและการแสดงแบบ runtime นั้นเหมือนกัน&strแต่มันยังเป็นเจ้าของstrซึ่งแตกต่างจาก&strแต่มันไม่สามารถปรับขนาดได้เพราะมันไม่ทราบความสามารถของมันดังนั้นโดยทั่วไป a Box<str>สามารถถูกมองว่าเป็นความยาวคงStringที่ แปลงเป็น a Stringหากคุณต้องการปรับขนาดเสมอ)

มีความสัมพันธ์ที่คล้ายกันมากระหว่าง[T]และVec<T>ยกเว้นไม่มีข้อ จำกัด UTF-8 และสามารถเก็บประเภทใดก็ได้ที่มีขนาดไม่ไดนามิก

การใช้strในระดับประเภทส่วนใหญ่เพื่อสร้าง abstractions ทั่วไปด้วย&str; มีอยู่ในระดับประเภทเพื่อให้สามารถเขียนคุณลักษณะได้อย่างสะดวก ในทางทฤษฎีแล้วstrสิ่งที่เป็นประเภทไม่จำเป็นต้องมีอยู่จริงเท่านั้น&strแต่นั่นหมายความว่าต้องมีการเขียนโค้ดพิเศษจำนวนมากซึ่งสามารถเป็นรหัสทั่วไปได้

&strมีประโยชน์อย่างยิ่งที่จะสามารถมีหลาย ๆ substrings ของStringโดยไม่ต้องคัดลอก; อย่างที่String เจ้าของบอกstrไว้บน heap มันจัดการและถ้าคุณสามารถสร้างซับสตริงของStringa ใหม่Stringได้มันจะต้องทำการคัดลอกเพราะทุกอย่างใน Rust สามารถมีเจ้าของเดียวเท่านั้นที่จะจัดการกับความปลอดภัยของหน่วยความจำได้ ตัวอย่างเช่นคุณสามารถแบ่งสตริง:

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

เรามีสตริงย่อยสองสตริงที่แตกต่างกันstrของสตริงเดียวกัน stringเป็นตัวที่เป็นเจ้าของstrบัฟเฟอร์เต็มจริงบนฮีปและ&strสตริงย่อยเป็นเพียงพอยน์เตอร์พอยน์เตอร์ไปยังบัฟเฟอร์นั้นบนฮีป


4

std::Stringu8เป็นเพียงเวกเตอร์ของ คุณสามารถค้นหาความหมายในรหัสที่มา มันเป็นกองที่จัดสรรและเติบโตได้

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

strเป็นชนิดดั้งเดิมที่เรียกว่าสตริงชิ้น ชิ้นสตริงมีขนาดคงที่ สตริงตัวอักษรเช่นlet test = "hello world"มี&'static strประเภท testเป็นการอ้างอิงถึงสตริงที่จัดสรรแบบสแตติกนี้ &strไม่สามารถแก้ไขได้เช่น

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

strจะมีชิ้นที่ไม่แน่นอน&mut strเช่น: 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);

แต่การเปลี่ยนแปลงเล็กน้อยเป็น UTF-8 สามารถเปลี่ยนความยาวไบต์ได้และชิ้นหนึ่งไม่สามารถจัดสรรการอ้างอิงได้


0

กล่าวง่ายๆStringคือประเภทข้อมูลถูกเก็บไว้ในกอง (เหมือนVec) และคุณสามารถเข้าถึงตำแหน่งนั้นได้

&strเป็นประเภทชิ้น นั่นหมายความว่ามันเป็นเพียงการอ้างอิงถึงปัจจุบันอยู่Stringที่ไหนสักแห่งในกอง

&strไม่ได้ทำการจัดสรรใด ๆ ที่รันไทม์ ดังนั้นสำหรับเหตุผลที่หน่วยความจำคุณสามารถใช้มากกว่า&str Stringแต่โปรดทราบว่าเมื่อใช้งาน&strคุณอาจต้องจัดการกับอายุการใช้งานที่ชัดเจน


1
บางแห่งในกอง - นั่นไม่ถูกต้องสมบูรณ์
Shepmaster

สิ่งที่ฉันหมายถึงคือstrมันมีviewอยู่แล้วStringในกอง
00imvj00

1
ฉันเข้าใจว่านั่นคือสิ่งที่คุณหมายถึงและฉันกำลังบอกว่าไม่ถูกต้องสมบูรณ์ "ฮีป" ไม่ใช่ส่วนหนึ่งของคำสั่ง
Shepmaster

-1

สำหรับคน C # และ Java:

  • สนิม ' String===StringBuilder
  • &str สตริง === (ไม่เปลี่ยนรูป) ของ Rust

ฉันชอบที่จะคิดว่า&strเป็นมุมมองในสตริงเช่นสตริง interned ใน Java / C # ที่คุณไม่สามารถเปลี่ยนได้เพียงสร้างขึ้นใหม่


1
ความแตกต่างที่ใหญ่ที่สุดระหว่างสตริง Java / C # และสตริง Rust คือ Rust guarentees สตริงที่จะถูกต้องยูนิโค้ดเช่นนี้ได้รับตัวอักษรตัวที่สามในสตริงต้องคิดมากกว่าเพียงแค่ "abc" [2] (เนื่องจากเราอาศัยอยู่ในโลกหลายภาษานี่เป็นสิ่งที่ดี)
Squirrel

นี้ไม่ถูกต้อง หัวข้อของความไม่แน่นอนได้รับการแก้ไขแล้วในคำตอบที่ได้รับคะแนนสูงสุด โปรดอ่านเพื่อเรียนรู้เพิ่มเติม
Shepmaster

-5

นี่คือคำอธิบายที่ง่ายและรวดเร็ว

String- โครงสร้างข้อมูลที่จัดสรรได้และสามารถจัดสรรได้มาก &strก็สามารถที่จะบังคับให้เป็น

str- คือ (ตอนนี้เป็นสนิมวิวัฒนาการ) สตริงที่มีความยาวคงที่ที่ไม่แน่นอนซึ่งอยู่บนฮีปหรือในไบนารี คุณเท่านั้นที่สามารถโต้ตอบกับเป็นชนิดที่ยืมมาผ่านมุมมองสตริงชิ้นเช่นstr&str

ข้อควรพิจารณาการใช้งาน:

ต้องการStringถ้าคุณต้องการเป็นเจ้าของหรือกลายพันธุ์สตริง - เช่นการส่งผ่านสตริงไปยังเธรดอื่นเป็นต้น

ชอบ&strถ้าคุณต้องการที่จะมีมุมมองแบบอ่านอย่างเดียวของสตริง


นี้ไม่ถูกต้อง หัวข้อของความไม่แน่นอนได้รับการแก้ไขแล้วในคำตอบที่ได้รับคะแนนสูงสุด โปรดอ่านเพื่อเรียนรู้เพิ่มเติม
Shepmaster
โดยการใช้ไซต์ของเรา หมายความว่าคุณได้อ่านและทำความเข้าใจนโยบายคุกกี้และนโยบายความเป็นส่วนตัวของเราแล้ว
Licensed under cc by-sa 3.0 with attribution required.