คำนำ : คำตอบนี้ถูกเขียนขึ้นก่อนที่จะเลือกในการสร้างขึ้นในลักษณะ -specifically ด้าน -were ดำเนินการ ฉันใช้เครื่องหมายคำพูดแบบบล็อกเพื่อระบุส่วนที่ใช้กับโครงร่างเก่าเท่านั้น (ส่วนที่ใช้เมื่อถามคำถาม)Copy
เก่า : ในการตอบคำถามพื้นฐานคุณสามารถเพิ่มช่องเครื่องหมายที่จัดเก็บNoCopy
ค่าได้ เช่น
struct Triplet {
one: int,
two: int,
three: int,
_marker: NoCopy
}
คุณยังสามารถทำได้โดยมีตัวทำลาย (ผ่านการใช้Drop
ลักษณะ ) แต่แนะนำให้ใช้ประเภทเครื่องหมายหากผู้ทำลายไม่ได้ทำอะไรเลย
ขณะนี้ประเภทจะย้ายไปตามค่าเริ่มต้นนั่นคือเมื่อคุณกำหนดประเภทใหม่จะไม่นำไปใช้Copy
เว้นแต่คุณจะนำไปใช้กับประเภทของคุณอย่างชัดเจน:
struct Triplet {
one: i32,
two: i32,
three: i32
}
impl Copy for Triplet {}
การดำเนินการเท่านั้นที่สามารถอยู่ถ้าทุกประเภทที่มีอยู่ในใหม่struct
หรือเป็นตัวเองenum
Copy
หากไม่เป็นเช่นนั้นคอมไพลเลอร์จะพิมพ์ข้อความแสดงข้อผิดพลาด นอกจากนี้ยังสามารถอยู่ถ้าชนิดไม่ได้มีDrop
การดำเนินการ
หากต้องการตอบคำถามที่คุณไม่ได้ถาม ... "เกิดอะไรขึ้นกับการเคลื่อนไหวและการคัดลอก":
อันดับแรกฉันจะกำหนด "สำเนา" ที่แตกต่างกันสองรายการ:
- สำเนาไบต์ซึ่งเป็นเพียงตื้นคัดลอกไบต์ไบต์โดยวัตถุชี้ไม่ได้ต่อไปเช่นถ้าคุณมี
(&usize, u64)
มันคือ 16 ไบต์บนคอมพิวเตอร์ 64 บิตและสำเนาตื้นจะเอาบรรดา 16 ไบต์และจำลองของพวกเขา ค่าในหน่วยความจำ 16 ไบต์อื่น ๆโดยไม่ต้องแตะusize
ที่ปลายอีกด้านหนึ่งของ&
. memcpy
นั่นคือมันเป็นเทียบเท่ากับการโทร
- สำเนาความหมาย , การทำซ้ำค่าที่จะสร้างใหม่เช่น (ค่อนข้าง) อิสระที่สามารถใช้ได้อย่างปลอดภัยแยกต่างหากเพื่อคนเก่า เช่นสำเนาเชิงความหมายของความหมายที่
Rc<T>
เกี่ยวข้องกับการเพิ่มจำนวนการอ้างอิงและสำเนาความหมายของการVec<T>
สร้างการจัดสรรใหม่จากนั้นคัดลอกองค์ประกอบที่จัดเก็บไว้ตามความหมายจากเก่าไปยังใหม่ สิ่งเหล่านี้อาจเป็นสำเนาลึก (เช่นVec<T>
) หรือตื้น (เช่นRc<T>
ไม่สัมผัสกับสิ่งที่จัดเก็บไว้T
) Clone
ถูกกำหนดไว้อย่างหลวม ๆ ว่าเป็นงานจำนวนน้อยที่สุดที่จำเป็นในการคัดลอกค่าประเภทT
จากภายใน a &T
ถึงT
ด้วยความหมาย
Rust ก็เหมือนกับ C ทุก ๆการใช้ค่าทีละค่าคือสำเนาไบต์:
let x: T = ...;
let y: T = x;
fn foo(z: T) -> T {
return z
}
foo(y)
เป็นสำเนาไบต์ไม่ว่าจะT
เคลื่อนไหวหรือ " คัดลอกได้โดยปริยาย" (เพื่อความชัดเจนพวกเขาไม่จำเป็นต้องทำสำเนาแบบไบต์ต่อไบต์ตามตัวอักษรในขณะรันไทม์: คอมไพเลอร์มีอิสระที่จะปรับสำเนาให้เหมาะสมที่สุดหากพฤติกรรมของโค้ดยังคงอยู่)
อย่างไรก็ตามมีปัญหาพื้นฐานเกี่ยวกับสำเนาไบต์: คุณพบค่าที่ซ้ำกันในหน่วยความจำซึ่งอาจเป็นผลเสียมากหากมีตัวทำลายเช่น
{
let v: Vec<u8> = vec![1, 2, 3];
let w: Vec<u8> = v;
}
หากw
เป็นเพียงสำเนาไบต์ธรรมดาv
ก็จะมีเวกเตอร์สองตัวชี้ไปที่การจัดสรรเดียวกันทั้งที่มีตัวทำลายที่ทำให้มันเป็นอิสระ ... ทำให้เกิดการฟรีสองเท่าซึ่งเป็นปัญหา NB. นี่จะดีมากถ้าเราทำสำเนาความหมายv
ลงไปw
ตั้งแต่นั้นมาก็w
จะเป็นอิสระของตัวเองVec<u8>
และผู้ทำลายจะไม่เหยียบย่ำซึ่งกันและกัน
มีการแก้ไขที่เป็นไปได้บางประการที่นี่:
- ปล่อยให้โปรแกรมเมอร์จัดการมันเช่น C. (ไม่มีตัวทำลายใน C ดังนั้นมันก็ไม่เลวร้ายเท่าไหร่ ... คุณแค่ปล่อยให้หน่วยความจำรั่วไหลแทน: P)
- ทำการคัดลอกความหมายโดยปริยายเพื่อให้
w
มีการจัดสรรของตัวเองเช่น C ++ พร้อมตัวสร้างสำเนา
- พิจารณาตามมูลค่าใช้เป็นการโอนความเป็นเจ้าของดังนั้นจึง
v
ไม่สามารถใช้งานได้อีกต่อไปและไม่มีตัวทำลายล้าง
สุดท้ายคือสิ่งที่ Rust ทำ: การย้ายเป็นเพียงการใช้ตามค่าโดยที่แหล่งที่มาไม่ถูกต้องแบบคงที่ดังนั้นคอมไพเลอร์จึงป้องกันไม่ให้ใช้หน่วยความจำที่ไม่ถูกต้องในขณะนี้ต่อไป
let v: Vec<u8> = vec![1, 2, 3];
let w: Vec<u8> = v;
println!("{}", v);
ประเภทที่มีตัวทำลายต้องย้ายเมื่อใช้ตามค่า (หรือที่เรียกว่าเมื่อคัดลอกไบต์) เนื่องจากมีการจัดการ / ความเป็นเจ้าของทรัพยากรบางอย่าง (เช่นการจัดสรรหน่วยความจำหรือที่จับไฟล์) และเป็นไปได้ยากมากที่สำเนาไบต์จะทำซ้ำได้อย่างถูกต้อง ความเป็นเจ้าของ.
"อืม ... สำเนาโดยนัยคืออะไร"
ลองนึกถึงประเภทดั้งเดิมเช่นu8
: สำเนาไบต์เป็นเรื่องง่ายเพียงแค่คัดลอกไบต์เดียวและสำเนาความหมายก็ทำได้ง่ายเพียงแค่คัดลอกไบต์เดียว โดยเฉพาะอย่างยิ่งสำเนาไบต์เป็นสำเนาความหมาย ... Rust ยังมีลักษณะในตัวCopy
ที่จับได้ว่าประเภทใดมีสำเนาความหมายและไบต์ที่เหมือนกัน
ดังนั้นสำหรับCopy
การใช้งานตามค่าประเภทเหล่านี้จะมีการคัดลอกความหมายโดยอัตโนมัติด้วยดังนั้นจึงปลอดภัยอย่างยิ่งที่จะใช้แหล่งที่มาต่อไป
let v: u8 = 1;
let w: u8 = v;
println!("{}", v);
เก่า : NoCopy
เครื่องหมายจะแทนที่พฤติกรรมอัตโนมัติของคอมไพเลอร์ในการสมมติว่าประเภทที่สามารถเป็นได้Copy
(กล่าวคือมีเฉพาะการรวมของไพรมารีและ&
) Copy
เท่านั้น อย่างไรก็ตามสิ่งนี้จะเปลี่ยนแปลงไปเมื่อมีการใช้คุณลักษณะในตัวของการเลือกใช้
ดังที่ได้กล่าวมาแล้วการเลือกใช้คุณลักษณะในตัวจะถูกนำมาใช้ดังนั้นคอมไพเลอร์จึงไม่มีพฤติกรรมอัตโนมัติอีกต่อไป อย่างไรก็ตามกฎที่ใช้สำหรับพฤติกรรมอัตโนมัติในอดีตเป็นกฎเดียวกันในการตรวจสอบว่าถูกต้องตามกฎหมายCopy
หรือไม่