有没有办法切换返回类型,以便编译器知道返回类型和匹配类型是相同的?

问题描述:

如何避免在下面的代码中使用不安全的代码?它意味着成为实体组件系统库的一部分。更一般地说,有没有办法在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

你可以这样说:

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 ... –