Python程序员体验Rust:第一天笑看Hello World,第三周哭对Borrow Checker

向量数据库大模型NoSQL数据库

前言

最近尝试着让大模型跨语言转换代码,效果不是太好,大众逻辑还行,其它还是要人工核验去手动编程的。以下是转换过程遇到的一些坑的实战记录。

Python


 
 
 
 
   
 selector: Sequence[str]

Rust

在Rust中我觉得最匹配这个数据类型的是Rust的切片。


 
 
 
 
   
let selector = ["conversation".to\_string(), var.name.to\_owned()].as\_slice();

继承问题
Rust没有继承
所以实现此结构只有两种选择: 1. 组合 2.trait

Python


 
 
 
 
   
class Segment(BaseModel):  
    model\_config = ConfigDict(frozen=True)  
  
    value\_type: SegmentType  
    value: Any  
  
class Variable(Segment):  
    id: str = Field(  
        default=lambda \_: str(uuid4()),  
        description="Unique identity for variable.",  
    )  
    name: str  
    description: str = Field(default="", description="Description of the variable.")  
    selector: Sequence[str] = Field(default\_factory=list)  
   

Rust

两种方案的选择建议:
● 如果需要直接访问字段,使用组合 + Deref
● 如果需要多态特性,增加 Trait 实现
● 根据实际场景可以组合使用两种方案

组合方案


 
 
 
 
   
#[derive(Debug, Serialize, Deserialize,Clone)]  
pub struct Variable {  
    segment: Segment,  
    id: String,  
    name: String,  
    description: String,  
    selector: Vec<String>,  
}

trait 方案


 
 
 
 
   
trait SegmentTrait {  
    fn value\_type(&self) -> &SegmentType  
    fn value(&self) -> &Value  
}  
  
#[derive(Debug, Serialize, Deserialize,Clone)]  
pub struct Variable {  
    segment: Segment,  
    id: String,  
    name: String,  
    description: String,  
    selector: Vec<String>,  
}  
impl SegmentTrait for Variable {  
    fn value\_type(&self) -> &SegmentType {  
        &self.segment.value\_type  
    }  
  
    fn value(&self) -> &SegmentValue {  
        &self.segment.value  
    }  
}

对于结构体的封装不建议使用多态,无法像方法体一样可以静态分发不丢失性能,故推荐用组合方式。

实体映射

python


 
 
 
 
   
SEGMENT\_TO\_VARIABLE\_MAP = {  
    StringSegment: StringVariable,  
    IntegerSegment: IntegerVariable,  
    FloatSegment: FloatVariable,  
    ObjectSegment: ObjectVariable,  
    FileSegment: FileVariable,  
    ArrayStringSegment: ArrayStringVariable,  
    ArrayNumberSegment: ArrayNumberVariable,  
    ArrayObjectSegment: ArrayObjectVariable,  
    ArrayFileSegment: ArrayFileVariable,  
    ArrayAnySegment: ArrayAnyVariable,  
    NoneSegment: NoneVariable,  
}

在Pytho中定义是相当简单的,由于Rust没有继承,所以封装这个映射关系要借助枚举来实现,实际在操作转换时过于繁琐,于是直接使用Rust的match大法实现。
首先定义各segment的枚举用于匹配具体的变量


 
 
 
 
   
#[derive(Debug, Clone, Serialize, Deserialize, Display, EnumString)]  
#[strum(serialize\_all = "snake\_case")]  
pub enum SegmentType {  
    NUMBER,  
    STRING,  
    OBJECT,  
    SECRET,  
    FILE,  
    #[strum(serialize = "array[any]")]  
    ArrayAny,  
    #[strum(serialize = "array[string]")]  
    ArrayString,  
    #[strum(serialize = "array[number]")]  
    ArrayNumber,  
    #[strum(serialize = "array[object]")]  
    ArrayObject,  
    #[strum(serialize = "array[file]")]  
    ArrayFile,  
    NONE,  
    GROUP,  
}

以StringVariable为例查看如何封装合适的结构


 
 
 
 
   
class Segment(BaseModel):  
    model\_config = ConfigDict(frozen=True)  
    value\_type: SegmentType  
    value: Any  
class StringSegment(Segment):  
    value\_type: SegmentType = SegmentType.STRING  
    value: str  
class ArrayAnySegment(ArraySegment):  
    value\_type: SegmentType = SegmentType.ARRAY\_ANY  
    value: Sequence[Any]

遇到这种结构想转换为Rust相当头痛,Python的数据结构约束太灵活的,灵活到随心所欲,阅读起来极其费劲,所以我才觉得Python是最烂的语言,原Python返回值是Any,在Rust要能代表Any只能转成Value或者使用枚举,返回枚举值。进一步观察Python的数据结构发现每个子Segment只是value_type不一样。因此先封装Segment类。由于serde_json自带的Value无法区分整数和小数,因此单独定义SegmentValue


 
 
 
 
   
#[derive(Debug, Clone, Serialize, Deserialize)]  
pub struct Segment {  
    value\_type:SegmentType,  
    value: SegmentValue  
}

枚举所有SegmentValue可能的值


 
 
 
 
   
#[derive(Debug, Serialize, Deserialize,Clone)]  
#[serde(untagged)]  
pub enum SegmentValue {  
    None,  
    String(String),  
    Float(f64),  
    Integer(i64),  
    Object(HashMap<String, serde\_json::Value>),  
    ArrayAny(Vec<serde\_json::Value>),  
    ArrayString(Vec<String>),  
    ArrayNumber(Vec<Number>),  
    ArrayObject(Vec<HashMap<String, serde\_json::Value>>),  
    ArrayFile(Vec<File>),  
    File(File),  
}

再定义最终返回的子类


 
 
 
 
   
#[derive(Debug, Clone, Serialize, Deserialize)]  
pub struct Variable {  
    pub segment: Segment,  
    pub id: String,  
    pub name: String,  
    pub description: String,  
    pub selector: Vec<String>,  
}

最后是序列化在Python中使用StringVariable.model_validate(mapping)完成校验并返回序列化后的内容。在使用Pydanticmodel_validate方法将原始数据(如字典/JSON)动态转换为经过验证的模型实例。
而Rust中的替代方案为: Validator + Serde


 
 
 
 
   
match value\_type {  
      SegmentType::NUMBER => { Variable::new\_number(&mapping)}  
      SegmentType::STRING => { Variable::new\_string(&mapping) }  
      SegmentType::OBJECT => {Variable::new\_object(&mapping)}  
      SegmentType::SECRET => {Variable::new\_secret(&mapping)}  
      SegmentType::ArrayString => {Variable::new\_array\_string(&mapping)}  
      SegmentType::ArrayNumber => {Variable::new\_array\_number(&mapping)}  
      SegmentType::ArrayObject => {Variable::new\_array\_object(&mapping)}  
      \_ => {  
       return Err(ApiErr::ValidationError("un supported segment type".to\_string()));  
           }  
      }

在处理number类型时,需要根据真实的类型来做区分


 
 
 
 
   
pub fn new\_number(mapping: ⤅<String, Value>) -> Self {  
        let num = mapping.get("value").unwrap();  
        let sv = if let Some(num) = num.as\_i64() {  
            SegmentValue::Integer(num)  
        }else if let Some(num) = num.as\_f64(){  
            SegmentValue::Float(num)  
        }else {  
            panic!("数字转换异常");  
        };  
        Self {  
            segment: Segment{  
                value\_type: SegmentType::NUMBER,  
                value: sv  
            },  
            id: mapping.get("id").unwrap().as\_str().unwrap().to\_string(),  
            name: mapping.get("name").unwrap().as\_str().unwrap().to\_string(),  
            description: mapping.get("description").unwrap().as\_str().unwrap().to\_string(),  
            selector: mapping.get("selector").unwrap().as\_array().unwrap().iter().map(|s| s.as\_str().unwrap().to\_string()).collect(),  
        }  
    }

结语

再回过头来看TypeSctipt为什么用Go而不是Rust重写,跨语言重构没点技术功底和对业务足够的理解,很容易翻车。所以Rust高性能和易用之间的选择,有时候并不是不能折衷。

0
0
0
0
关于作者
关于作者

文章

0

获赞

0

收藏

0

相关资源
火山引擎大规模机器学习平台架构设计与应用实践
围绕数据加速、模型分布式训练框架建设、大规模异构集群调度、模型开发过程标准化等AI工程化实践,全面分享如何以开发者的极致体验为核心,进行机器学习平台的设计与实现。
相关产品
评论
未登录
看完啦,登录分享一下感受吧~
暂无评论