2016-04-11 7 views
6

yeni bir çeşidi ile alan taşıyarak:değiştir enum varyantı ise herhangi klonlama olmadan yenisine eski varyantın bir alan taşırken bir enum varyantı güncellemek istiyorum

enum X { 
    X1(String), 
    X2(String), 
} 

fn increment_x(x: &mut X) { 
    match x { 
     &mut X::X1(s) => { 
      *x = X::X2(s); 
     } 
     &mut X::X2(s) => { 
      *x = X::X1(s); 
     } 
    } 
} 

Bunun nedeni çalışmıyor s'u &mut X'dan kaldıramazız. Bir enum X { X1, X2 } uygulanması ve bu basitleştirilmiş bir örnektir vb struct S { variant: X, str: String } kullanmak gibi

önermek etmeyin şeyler varyantları diğer alanlara çok sahip ve başka bir varyantı olan bir alan taşımak isteyen düşünün.

+3

String' 'durumunda ... yerine dışarı faktoring @Shepmaster için, sen' mem alana boş bir dize replace' ve sonucu kullanabilirsiniz :: edebilirsiniz yeni varyantı oluşturmak için. Sadece birkaç hamle. Ancak bu sadece boş dizeler gibi ucuz bir forma sahip türler için çalışır. –

cevap

7

&mut X'dan s'u taşıyamadığımız için bu işe yaramıyor.

Sonra yeni bir ... bunu değere göre yapı alıp dönmek yoktur: Eğer dışarı dize hareket edebilir çünkü eğer

enum X { 
    X1(String), 
    X2(String), 
} 

fn increment_x(x: X) -> X { 
    match x { 
     X::X1(s) => X::X2(s), 
     X::X2(s) => X::X1(s), 
    } 
} 

Sonuçta, derleyici sizi koruyor sayımın, daha sonra yarım inşa edilmiş bir durumda olurdu. Fonksiyonun o anda panik yapması durumunda ipi serbest bırakmaktan kim sorumlu olurdu? Enumdaki dizgiyi veya yerel değişkendeki dizgiyi serbest bırakmalıdır. İki katına çıkılamazsa, bellek güvenliği sorunudur.

bir değişken referans üzerinde uygulamak zorunda olduğu sen, orada geçici bir kukla değeri saklamak olsaydı: Boş String (sadece birkaç işaretçiler var çok fena değil oluşturma

use std::mem; 

fn increment_x_inline(x: &mut X) { 
    let old = mem::replace(x, X::X1(String::new())); 
    *x = increment_x(old); 
} 

, hiçbir yığın tahsis), ancak her zaman mümkün değildir. Bu durumda, Option kullanabilirsiniz: Eğer bir sıfır maliyetli bir şekilde değer dışına taşımadan bunu yapmak istiyorsanız

fn increment_x_inline(x: &mut Option<X>) { 
    let old = x.take(); 
    *x = old.map(increment_x); 
} 
3

, siz (AFAIK) güvensiz kod biraz başvurmak zorunda:

#[derive(Debug)] 
enum X { 
    X1(String), 
    X2(String), 
} 

fn increment_x(x: &mut X) { 
    let interim = unsafe { mem::uninitialized() }; 
    let prev = mem::replace(x, interim); 
    let next = match prev { 
     X::X1(s) => X::X2(s), 
     X::X2(s) => X::X1(s), 
    }; 
    let interim = mem::replace(x, next); 
    mem::forget(interim); // Important! interim was never initialized 
} 

Düzenleme: Teşekkür

+0

Burada ** çok dikkatli olmalısınız. Mem :: uninitialized() 've' mem :: forget 'arasında gerçekleşen panikler, başlatılmamış değerin programın geri kalanına sızmasına izin verecektir. Bu örnekte, * Ben * böyle bir olasılık görmüyorum. Ancak, birilerinin panik oluşturabilecek kodları arayabilmesi için örneği değiştirmesi çok olasıdır. FWIW, [ikinci 'mem :: replace'] 'i çıkartacağım (https://play.integer32.com/?gist=46b4233e57b243819bf0ef2bd1cda799&version=stable). – Shepmaster

+0

Açıkçası, ne olursa olsun, 'güvensiz' koduna çok dikkat etmeniz gerekir. Bunu performans açısından kritik bir yolda yapmam gerekiyordu, bu yüzden 'güvensiz' gitmek için bir yoldu. Birisi orada önemsiz olmayan/potansiyel olarak panik yapan bir kod eklemek istiyorsa, bir "unutma nesnesi" yapmalıdırlar, yani. ara nesneyi saklayan ve 'drop() 'üzerinde unutmaya çalışan bir tanesi. – kralyk