Bir türü sadece taşınabilir ve kopyalanamaz hale getirmek mümkün müdür?


96

Editörün notu : Bu soru Rust 1.0'dan önce sorulmuştu ve sorudaki bazı iddialar Rust 1.0'da doğru olmayabilir. Her iki versiyonu da ele almak için bazı cevaplar güncellendi.

Bu yapıya sahibim

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

Bunu bir işleve aktarırsam, dolaylı olarak kopyalanır. Şimdi, bazen bazı değerlerin kopyalanamaz olduğunu ve bu nedenle taşınması gerektiğini okudum.

Bu yapıyı Tripletkopyalanamaz hale getirmek mümkün müdür ? Örneğin, Tripletkopyalanamaz ve dolayısıyla "taşınabilir" hale getirecek bir özelliği uygulamak mümkün olabilir mi?

Bir yerde, Cloneörtük olarak kopyalanamayan şeyleri kopyalamak için özelliğin uygulanması gerektiğini okudum, ancak bunun tam tersini asla okumadım, yani örtük olarak kopyalanabilen bir şeye sahip olmak ve onun yerine hareket etmesi için kopyalanamaz hale getirmek.

Bu mantıklı geliyor mu?


1
paulkoerbitz.de/posts/… . Burada neden kopyalamaya karşı hareket etmenin iyi açıklamaları var .
Sean Perry

Yanıtlar:


165

Önsöz : Bu cevap, yerleşik özellikler --özel olarak Copyyönler - uygulanmadan önce yazılmıştır . Yalnızca eski şemaya (soru sorulduğunda uygulanan) uygulanan bölümleri belirtmek için blok alıntılar kullandım.


Eski : Temel soruyu cevaplamak için, bir NoCopydeğer depolayan bir işaret alanı ekleyebilirsiniz . Örneğin

struct Triplet {
    one: int,
    two: int,
    three: int,
    _marker: NoCopy
}

Bunu bir yıkıcıya sahip olarak da yapabilirsiniz ( Dropözelliği uygulayarak ), ancak yıkıcı hiçbir şey yapmıyorsa işaretçi türlerini kullanmak tercih edilir.

Türler artık varsayılan olarak taşınır, yani yeni bir tür tanımladığınızda, türünüz için Copyaçıkça uygulamadıkça uygulanmaz:

struct Triplet {
    one: i32,
    two: i32,
    three: i32
}
impl Copy for Triplet {} // add this for copy, leave it out for move

Uygulama ancak her tür yenide yer alıyorsa structveya enumkendisiyse var olabilir Copy. Değilse, derleyici bir hata mesajı yazdıracaktır. Ayrıca, yalnızca türün bir uygulaması yoksa var olabilir Drop.


Sormadığınız soruyu yanıtlamak için ... "hareketler ve kopyalamada ne var?":

Öncelikle iki farklı "kopya" tanımlayacağım:

  • bir bayt kopyası , bir nesneyi bayt-bayt olarak basit bir şekilde kopyalayan, işaretçileri takip etmeyen bir bayt kopyası , örneğin eğer varsa (&usize, u64), 64-bit bir bilgisayarda 16 bayttır ve yüzeysel bir kopya, bu 16 baytı alıp kopyalar. bellek başka bir 16-bayt öbek değer, olmadan temas usizediğer ucunda &. Yani, aramaya eşdeğerdir memcpy.
  • Bir semantik kopya bir değer çoğaltarak, güvenli bir şekilde eski bir ayrı ayrı kullanılabilen bir yeni (biraz), bağımsız bir örneğini oluşturmak için. Örneğin, bir ifadenin anlamsal bir kopyası Rc<T>, sadece referans sayısını artırmayı içerir ve bir semantik kopyası, Vec<T>yeni bir tahsis oluşturmayı ve daha sonra depolanan her bir öğeyi eskiden yeniye semantik olarak kopyalamayı içerir. Bunlar olabilir derin kopyalar (örn Vec<T>) veya sığ (örn Rc<T>dokunmuyor saklı T), Cloneişin en küçük miktarı semantik türünde bir değer kopyalamak için gerekli olan gevşek tanımlanır Tbir içeriden &Tiçin T.

Rust, C gibidir, bir değerin her by-değer kullanımı bir bayt kopyasıdır:

let x: T = ...;
let y: T = x; // byte copy

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

foo(y) // byte copy

THareket etsin veya taşınmasın bayt kopyalarıdır veya "örtük olarak kopyalanabilir ". (Açık olmak gerekirse, çalışma zamanında kelimenin tam anlamıyla bayt bayt kopyalar olması gerekmez: derleyici, kodun davranışı korunursa kopyaları optimize etmekte özgürdür.)

Bununla birlikte, bayt kopyalarında temel bir sorun vardır: bellekte yinelenen değerler elde edersiniz; bu, yıkıcıları varsa çok kötü olabilir, örn.

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

Eğer wsadece düz bir bayt kopyası volsaydı, aynı tahsisi gösteren iki vektör olurdu, her ikisi de onu serbest bırakan yıkıcılara sahiptir ... çift ​​serbestliğe neden olur , ki bu bir problemdir. NB. Bunun anlamsal bir kopyasını viçine yaparsak, bu tamamen iyi olurdu w, çünkü o wzaman kendi bağımsız olacak Vec<u8>ve yıkıcılar birbirlerini çiğnemeyecekti.

Burada birkaç olası düzeltme var:

  • Bırakın, programcı C gibi işlesin (C'de yıkıcı yok, bu yüzden o kadar kötü değil ... bunun yerine sadece bellek sızıntıları kalıyor.: P)
  • wKopyalama yapıcılarıyla C ++ gibi kendi ayırmasına sahip olması için dolaylı olarak anlamsal bir kopya gerçekleştirin .
  • Bir mülkiyet devri olarak değer kullanımlarına bakıldığında, vartık kullanılamaz ve yıkıcı çalıştırılamaz.

Sonuncusu Rust'un yaptığı şeydir: bir hareket , kaynağın statik olarak geçersiz kılındığı bir değerde kullanımdır, bu nedenle derleyici artık geçersiz olan belleğin daha fazla kullanılmasını engeller.

let v: Vec<u8> = vec![1, 2, 3];
let w: Vec<u8> = v;
println!("{}", v); // error: use of moved value

Yıkıcıları olan türler , bazı kaynakların yönetimine / sahipliğine sahip olduklarından (örneğin, bir bellek tahsisi veya bir dosya tanıtıcısı) ve bir bayt kopyasının bunu doğru bir şekilde çoğaltması çok düşük bir ihtimal olduğundan, değer başına (bayt kopyalandığında) kullanıldığında hareket etmelidir. mülkiyet.

"Peki ... örtük kopya nedir?"

İlkel bir türü düşünün u8: Bir bayt kopyası basittir, sadece tek baytı kopyalayın ve anlamsal bir kopya da aynı basittir, tek baytı kopyalayın. Özellikle, bir bayt kopyası olan bir semantik kopya ... Pas bile sahip bir dahili özelliğiCopy olduğunu tipleri aynı semantik ve bayt kopyaları yakalar.

Bu nedenle, bu Copytürler için değer bazında kullanımlar otomatik olarak anlamsal kopyalardır ve bu nedenle kaynağı kullanmaya devam etmek tamamen güvenlidir.

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

Eski : NoCopyişaretleyici edilebileceğini türlerini varsayarak derleyicinin otomatik davranışını geçersiz kılar Copy(yani sadece ilkel toplulukları ihtiva ve &) vardır Copy. Ancak bu, isteğe bağlı yerleşik özellikler uygulandığında değişecektir .

Yukarıda belirtildiği gibi, isteğe bağlı yerleşik özellikler uygulanmıştır, bu nedenle derleyici artık otomatik davranışa sahip değildir. Bununla birlikte, geçmişte otomatik davranış için kullanılan kural, uygulanmasının yasal olup olmadığını kontrol etmek için aynı kurallardır Copy.


@dbaupp: Yerleşik özelliklerin hangi Rust sürümünde göründüğünü biliyor musunuz? 0.10 düşünüyorum.
Matthieu M.

@MatthieuM. henüz uygulanmadı ve aslında son zamanlarda yerleşik yerleşiklerin tasarımında önerilen bazı revizyonlar oldu .
huon

Sanırım bu eski alıntı silinmeli.
Stargateur

1
# [derive (Copy, Clone)] Triplet üzerinde kullanılmalı değil
impl

6

En kolay yol, kendi türünüze kopyalanamayan bir şeyi yerleştirmektir.

Standart kitaplık, tam olarak bu kullanım durumu için bir "işaret tipi" sağlar: NoCopy . Örneğin:

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

15
Bu Rust> = 1.0 için geçerli değildir.
malbarbo
Sitemizi kullandığınızda şunları okuyup anladığınızı kabul etmiş olursunuz: Çerez Politikası ve Gizlilik Politikası.
Licensed under cc by-sa 3.0 with attribution required.