2017-01-03 14 views
14

有幾次,我碰到了一個場景,在這個場景中,可變參數和不可變參考都需要訪問器方法。如何避免在Rust中爲可變和不可變引用編寫重複訪問器函數?

對於〜3行,複製邏輯不是問題,但是當邏輯變得更復雜時,複製粘貼大塊代碼並不好。

我希望能夠重新使用兩者的代碼。

Rust是否提供了一些方法來處理這個問題,然後複製粘貼代碼或使用unsafe強制轉換?

例如爲:

impl MyStruct { 
    pub fn get_foo(&self) -> &Bar { 
     // ~20 lines of code 
     // --- snip --- 
     return bar; 
    } 
    pub fn get_foo_mut(&mut self) -> &mut Bar { 
     // ~20 lines of code 
     // (exactly matching previous code except `bar` is mutable) 
     // --- snip --- 
     return bar; 
    } 
} 

這裏是一個代碼庫,其中一個不變的返回參數被轉換爲可變的,以支持功能的兩個可變和不可變版本的更詳細的摘錄。這使用包裝的指針類型(ConstPMutP用於不可變和可變引用),但函數的邏輯應該清楚。

pub fn face_vert_share_loop<V, F>(f: F, v: V) -> LoopConstP 
    where V: Into<VertConstP>, 
      F: Into<FaceConstP> 
{ 
    into_expand!(f, v); 

    let l_first = f.l_first.as_const(); 
    let mut l_iter = l_first; 
    loop { 
     if l_iter.v == v { 
      return l_iter; 
     } 

     l_iter = l_iter.next.as_const(); 
     if l_iter == l_first { 
      break; 
     } 
    } 

    return null_const(); 
} 
pub fn face_vert_share_loop_mut(f: FaceMutP, v: VertMutP) -> LoopMutP { 
    let l = face_vert_share_loop(f, v); 
    return unsafe { 
     // Evil! but what are the alternatives? 
     // Perform an unsafe `const` to `mut` cast :(
     // While in general this should be avoided, 
     // its 'OK' in this case since input is also mutable. 
     l.as_mut() 
    }; 
} 
+0

只是爲了澄清一下,'MyStruct'與某個地圖類似,並且您擁有的20行代碼用於獲取對Bar的引用?這可能是因爲現在是早晨,但是我看到這些事情究竟是如何發生的,所以很難評估我提出的潛在答案......你能想出一個MCVE嗎? –

+0

在我需要的地方添加了一個函數的例子。本來會發布一些更簡單和自包含的內容 - 但是通過簡化問題*可能會使用Rust的某些功能,不能用於更多涉及的情況。 – ideasman42

+1

現在你的例子更清晰了,謝謝。我在C++中遇到了同樣的問題,並且以與您的示例類似的方式定期使用'const_cast';我很想看看人們會做什麼,也許有些特質(與關聯類型)可以通過可變性進行抽象。 –

回答

7

你沒有,真的。回想一下,T,&T&mut T都是不同類型。在這種情況下,您的問題與「如何避免爲StringHashMap寫入重複訪問器函數」相同。

馬蒂厄中號有正確的術語 「抽象的過度易變性」:

TL; DR是Rust可能需要增加新功能來支持這一點。由於沒有人成功,沒有人百分之百地確定那些特徵是需要的。目前最好的猜測是更高的激活類型(HKT)。

-3

當前Rust不支持對可變性進行抽象。

有一些方法可以做到這一點,雖然他們並不理想:

  • 使用宏來擴大重複的代碼,聲明宏觀和兩個功能之間共享 - 需要構建所以當然適用於可變和不可變。
  • 編寫該函數的不可變版本(以確保沒有任何更改),然後爲可變版本編寫一個包裝函數,該函數對結果執行unsafe轉換以使其變爲可變。

這些都不是非常有吸引力的(宏過於冗長,有點不太可讀,添加一些代碼膨脹),該unsafe更具可讀性,但它會很高興,以避免因爲從一成不變的鑄造可變通過代碼庫不太好。

就我所能看到的(其中複製粘貼代碼不可接受)而言,現在是最好的選擇,就是編寫一個不可變的函數版本,然後用一個mut版本的函數包裝它,其中兩個輸入並且輸出是可變的。

這需要在該功能的輸出上鑄造一個unsafe,因此它並不理想。


注意:它有不可改變的功能包含代碼的身體,由於反向將允許什麼可能是一成不變的輸入偶然的不同誘變是很重要的。

+1

-1因爲創建一個虛假的'&mut'引用是UB;程序變得很難推理,並且你有可能失去Rust的主要好處:如果編譯的程序是內存安全的 – bluss

+2

當使用'unsafe'塊時,不存在關鍵不變量的豁免。記住Ms2ger的格言:*「並且注意,不安全的代碼並不是違反Rust的不變量,而是爲了手動維護它們。」*。 – bluss

+0

@ Shepmaster的回答表明這不被支持,那沒問題,但有些方法可以做到這一點,就像這個答案中包含的一樣。所以,即使它被視爲糟糕的選擇 - 它的價值仍然值得注意,它可以完成,並且*可能會更好,然後複製粘貼大塊代碼。 – ideasman42

3

(操場鏈接使用type parametersassociated types解決方案)

在這種情況下&T&mut T只是兩個不同的類型。通過不同類型(在編譯時和運行時)通用的代碼通常使用traits在Rust中編寫。例如,給定:

struct Foo { value: i32 } 
struct Bar { foo: Foo } 

假設我們要提供Bar與它Foo數據成員的通用訪問。訪問者應在&Bar&mut Bar上適當地返回&Foo&mut Foo。因此,我們寫一個特點FooGetter

trait FooGetter { 
    type Output; 
    fn get(self) -> Self::Output; 
} 

,他們的工作是要在特定類型的Bar,我們有通用的。其Output類型將取決於Bar,因爲我們想要get有時返回&Foo有時&mut Foo。另請注意,它消耗self類型Self。因爲我們想get是通用在&Bar&mut Bar我們需要實現FooGetter兩個,讓Self具有相應類型:

// FooGetter::Self == &Bar 
impl<'a> FooGetter for &'a Bar { 
    type Output = &'a Foo; 
    fn get(self) -> Self::Output { & self.foo } 
} 

// FooGetter::Self == &mut Bar 
impl<'a> FooGetter for &'a mut Bar { 
    type Output = &'a mut Foo; 
    fn get(mut self) -> Self::Output { &mut self.foo } 
} 

現在我們可以很容易地使用.get()在通用的代碼,即可獲得&&mut引用Foo&Bar&mut Bar(僅需要T: FooGetter)。例如:

// exemplary generic function: 
fn foo<T: FooGetter>(t: T) -> <T as FooGetter>::Output { 
    t.get() 
} 

fn main() { 
    let x = Bar { foo: Foo {value: 2} }; 
    let mut y = Bar { foo: Foo {value: 2} }; 

    foo(&mut y).value = 3; 
    println!("{} {}\n", foo(&x).value, foo(&mut y).value); 
} 

請注意,您也可以實現FooGetterBar,使get是通用在&T&mut TT本身(在移動它)。這實際上是如何在標準庫中實現.iter()方法的,以及它爲什麼始終做到「正確的事情」而不依賴於它所調用的參數的參考性。

+0

似乎這隻適用於方法? (拿自己),但我想這些功能可以打包並調用方法。 – ideasman42

+0

@ ideasman42是的,請參閱上面最後一個代碼片段中的'foo'函數,瞭解如何在自由函數中包裝方法調用。你可以在任意函數中包裝任何對象方法/特徵方法。請注意,我們必須使用方法,因爲你想要什麼需要「函數重載」(如不同的'.get'函數根據參數的類型被調用),這是不可用於免費函數的。特別是,這並不侷限於第一個參數的類型,因爲您可以使用關聯的類型來重載其他參數的類型,也可以在夜間重載。 – gnzlbg

相關問題