2016-03-28 24 views
1

如何避免在下面的代碼中使用不安全的代碼?它意味着成爲實體組件系統庫的一部分。更一般地說,有沒有辦法在Rust中以返回類型切換的方式來讓編譯器在塊內部知道返回類型和匹配類型是相同的?有沒有辦法切換返回類型,以便編譯器知道返回類型和匹配類型是相同的?

use std::any::{Any, TypeId}; 
use std::mem; 

#[derive(Debug)] struct Health(f64); 
#[derive(Debug)] struct Position([f64; 3]); 

trait Entity { 
    fn get<'a, T: Any>(&self) -> Option<&'a T>; 
} 

#[derive(Debug)] 
struct Pig { 
    health: Health, 
    position: Position, 
} 

impl Entity for Pig { 
    fn get<'a, T: Any>(&self) -> Option<&'a T> { 
     if TypeId::of::<T>() == TypeId::of::<Health>() { 
      Some(unsafe {mem::transmute(&self.health)}) 
     } else if TypeId::of::<T>() == TypeId::of::<Position>() { 
      Some(unsafe {mem::transmute(&self.position)}) 
     } else { None } 
    } 
} 

fn main() { 
    let waddles = Pig { 
     health: Health(2.0), 
     position: Position([1.0, 2.0, 3.0]), 
    }; 

    println!("Waddles' Health: {:?}", waddles.get::<Health>()); 
} 

gist

回答

2

你可以這樣說:

fn get<T: Any>(&self) -> Option<&T> { 
    if let Some(health) = Any::downcast_ref::<T>(&self.health) { 
     Some(&health) 
    } 
    else if let Some(position) = Any::downcast_ref::<T>(&self.position) { 
     Some(&position) 
    } else { 
     None 
    } 
} 

請注意,我也刪除從函數頭明確壽命(在特徵定義,太)。在這種情況下,終生省略工作,因爲輸出生命週期必然會影響輸入生命週期(self)。

上面的代碼非常冗長,並且有很多重複的代碼。因此,它可能是有用的,寫一個簡單的宏它:

macro_rules! entity_match { 
    ($self_:ident; $($entity:ident),*) => {{ 
     $(if let Some(inner) = Any::downcast_ref::<T>(&$self_.$entity) { 
      return Some(&inner); 
     })* 
     None 
    }} 
} 

impl Entity for Pig { 
    fn get<T: Any>(&self) -> Option<&T> { 
     entity_match!(self; health, position) 
    } 
} 

作爲一個小紙條:我認爲這將是相當不錯的在這裏使用的編譯器插件,以紀念一些結構成員在結構定義的實體。

+0

哇,很高興知道,我計劃學習編譯器插件與此項目:)呃,你可能已經知道,但你可以避免與任何:: downcast_ref(&self.health)演員,而不是,這是甚至清潔器。謝謝! – Shien

+0

這應該可能是另一個問題,但你有什麼辦法可以在特質對象中使用它嗎?我的意思是,我應該首先想到這一點。 – Shien

+0

@Shien這很難。您也需要將組件作爲特徵對象返回。但正如你所說,這足以解決另一個問題 - 也許在Rust用戶論壇上,而不是SO ... –

相關問題