2016-04-11 24 views
6

我想更新一個枚舉變型,而移動舊的變體的場到新的無任何形式的克隆:更改枚舉變異而本場移動到新的變種

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); 
     } 
    } 
} 

這並不因爲工作我們不能從&mut X移動s

樣實現一個enum X { X1, X2 }使用struct S { variant: X, str: String }等,這是一個簡單的例子請不要建議的事情,試想有許多變種在其他領域的,和想要一個字段從一個變體移動到另一個。

+3

在String'的'的情況下,可以'MEM :: replace'一個空字符串到現場,並使用結果以形成新的變體。只需幾步。但是,這隻適用於類似空字符串的廉價形式。 –

回答

7

這不起作用,因爲我們不能從&mut X移動s

那就不要做...通過值採取結構並返回一個新問題:

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), 
    } 
} 

最終,編譯器保護你,因爲如果你可以移出串的枚舉,那麼它將處於某種半建造狀態。如果該功能在那個時刻出現恐慌,誰將負責釋放該字符串?是否應釋放enum中的字符串或局部變量中的字符串。它不可能是一個雙免費的內存安全問題。

如果你要實現它在一個可變的參考,你可以存儲在那裏暫時虛值:

use std::mem; 

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

創建空String是不是太糟糕(這只是幾個三分球,沒有堆分配),但並不總是可能的。在這種情況下,你可以使用Option

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

如果你想這樣做,而不在零成本的方式移出值,你不得不求助於有點不安全的代碼(據我所知):

#[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 
} 

編輯:感謝@Shepmaster用於分解出替代...

+0

這裏必須要非常小心**。在mem :: uninitialized()和mem :: forget之間發生的任何恐慌將允許未初始化的值泄漏到程序的其餘部分。在這個例子中,* I *沒有看到這樣的可能性。但是,有人很可能會修改示例以便能夠調用可能導致恐慌的代碼。 FWIW,我會[提取第二個'mem :: replace'](https://play.integer32.com/?gist=46b4233e57b243819bf0ef2bd1cda799&version=stable)。 – Shepmaster

+0

顯然,無論如何,你都需要對'unsafe'代碼非常小心。我需要在性能關鍵路徑上做到這一點,所以「不安全」就是要走的路。如果有人想在那裏插入一些不平凡的/可能的恐慌代碼,他們應該做一個「遺忘對象」,即。一個存儲臨時對象並將其忘在'drop()'上。 – kralyk