เป็นไปได้ไหมที่จะทำให้ประเภทที่เคลื่อนย้ายได้และไม่สามารถคัดลอกได้


96

หมายเหตุบรรณาธิการ : คำถามนี้ถูกถามก่อน Rust 1.0 และคำยืนยันบางส่วนในคำถามนั้นไม่จำเป็นต้องเป็นจริงใน Rust 1.0 คำตอบบางส่วนได้รับการอัปเดตเพื่อระบุทั้งสองเวอร์ชัน

ฉันมีโครงสร้างนี้

struct Triplet {
    one: i32,
    two: i32,
    three: i32,
}

ถ้าฉันส่งสิ่งนี้ไปยังฟังก์ชันฟังก์ชันนั้นจะถูกคัดลอกโดยปริยาย ตอนนี้บางครั้งฉันอ่านว่าค่าบางค่าไม่สามารถคัดลอกได้จึงต้องย้าย

เป็นไปได้ไหมที่จะทำให้โครงสร้างนี้Tripletไม่สามารถคัดลอกได้ ตัวอย่างเช่นเป็นไปได้ไหมที่จะใช้ลักษณะที่ทำให้Tripletไม่สามารถคัดลอกได้และทำให้ "เคลื่อนย้ายได้"

ฉันอ่านที่ไหนสักแห่งที่ต้องใช้Cloneลักษณะในการคัดลอกสิ่งที่ไม่สามารถคัดลอกได้โดยปริยาย แต่ฉันไม่เคยอ่านเกี่ยวกับวิธีอื่นนั่นคือมีบางสิ่งที่สามารถคัดลอกได้โดยปริยายและทำให้ไม่สามารถคัดลอกได้เพื่อให้ย้ายไปแทน

มันสมเหตุสมผลหรือไม่?


1
paulkoerbitz.de/posts/… . คำอธิบายที่ดีในที่นี้ว่าทำไมต้องย้ายกับสำเนา
Sean Perry

คำตอบ:


165

คำนำ : คำตอบนี้ถูกเขียนขึ้นก่อนที่จะเลือกในการสร้างขึ้นในลักษณะ -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 {} // add this for copy, leave it out for move

การดำเนินการเท่านั้นที่สามารถอยู่ถ้าทุกประเภทที่มีอยู่ในใหม่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; // byte copy

fn foo(z: T) -> T {
    return z // byte copy
}

foo(y) // byte copy

เป็นสำเนาไบต์ไม่ว่าจะTเคลื่อนไหวหรือ " คัดลอกได้โดยปริยาย" (เพื่อความชัดเจนพวกเขาไม่จำเป็นต้องทำสำเนาแบบไบต์ต่อไบต์ตามตัวอักษรในขณะรันไทม์: คอมไพเลอร์มีอิสระที่จะปรับสำเนาให้เหมาะสมที่สุดหากพฤติกรรมของโค้ดยังคงอยู่)

อย่างไรก็ตามมีปัญหาพื้นฐานเกี่ยวกับสำเนาไบต์: คุณพบค่าที่ซ้ำกันในหน่วยความจำซึ่งอาจเป็นผลเสียมากหากมีตัวทำลายเช่น

{
    let v: Vec<u8> = vec![1, 2, 3];
    let w: Vec<u8> = v;
} // destructors run here

หาก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); // error: use of moved value

ประเภทที่มีตัวทำลายต้องย้ายเมื่อใช้ตามค่า (หรือที่เรียกว่าเมื่อคัดลอกไบต์) เนื่องจากมีการจัดการ / ความเป็นเจ้าของทรัพยากรบางอย่าง (เช่นการจัดสรรหน่วยความจำหรือที่จับไฟล์) และเป็นไปได้ยากมากที่สำเนาไบต์จะทำซ้ำได้อย่างถูกต้อง ความเป็นเจ้าของ.

"อืม ... สำเนาโดยนัยคืออะไร"

ลองนึกถึงประเภทดั้งเดิมเช่นu8: สำเนาไบต์เป็นเรื่องง่ายเพียงแค่คัดลอกไบต์เดียวและสำเนาความหมายก็ทำได้ง่ายเพียงแค่คัดลอกไบต์เดียว โดยเฉพาะอย่างยิ่งสำเนาไบต์เป็นสำเนาความหมาย ... Rust ยังมีลักษณะในตัวCopyที่จับได้ว่าประเภทใดมีสำเนาความหมายและไบต์ที่เหมือนกัน

ดังนั้นสำหรับCopyการใช้งานตามค่าประเภทเหล่านี้จะมีการคัดลอกความหมายโดยอัตโนมัติด้วยดังนั้นจึงปลอดภัยอย่างยิ่งที่จะใช้แหล่งที่มาต่อไป

let v: u8 = 1;
let w: u8 = v;
println!("{}", v); // perfectly fine

เก่า : NoCopyเครื่องหมายจะแทนที่พฤติกรรมอัตโนมัติของคอมไพเลอร์ในการสมมติว่าประเภทที่สามารถเป็นได้Copy(กล่าวคือมีเฉพาะการรวมของไพรมารีและ&) Copyเท่านั้น อย่างไรก็ตามสิ่งนี้จะเปลี่ยนแปลงไปเมื่อมีการใช้คุณลักษณะในตัวของการเลือกใช้

ดังที่ได้กล่าวมาแล้วการเลือกใช้คุณลักษณะในตัวจะถูกนำมาใช้ดังนั้นคอมไพเลอร์จึงไม่มีพฤติกรรมอัตโนมัติอีกต่อไป อย่างไรก็ตามกฎที่ใช้สำหรับพฤติกรรมอัตโนมัติในอดีตเป็นกฎเดียวกันในการตรวจสอบว่าถูกต้องตามกฎหมายCopyหรือไม่


@dbaupp: คุณจะรู้หรือไม่ว่า Rust รุ่นใดที่คุณสมบัติในตัวที่เลือกใช้ปรากฏขึ้น? ฉันจะคิดว่า 0.10
Matthieu M.

@MatthieuM. มันจะยังไม่ได้ใช้และมีจริงเมื่อเร็ว ๆ นี้บางส่วนเนื้อเรื่องที่นำเสนอการออกแบบของเลือกในการสร้างอิน
huon

ฉันคิดว่าคำพูดเก่า ๆ ควรถูกลบทิ้ง
Stargateur

1
# [ได้มา (Copy, Clone)] ควรใช้กับ Triplet not
im

6

วิธีที่ง่ายที่สุดคือการฝังบางสิ่งในประเภทของคุณที่ไม่สามารถคัดลอกได้

มาตรฐานห้องสมุดให้ "ประเภทเครื่องหมาย" สำหรับตรงนี้กรณีการใช้งาน: NoCopy ตัวอย่างเช่น:

struct Triplet {
    one: i32,
    two: i32,
    three: i32,
    nocopy: NoCopy,
}

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