前言
最近尝试着让大模型跨语言转换代码,效果不是太好,大众逻辑还行,其它还是要人工核验去手动编程的。以下是转换过程遇到的一些坑的实战记录。
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高性能和易用之间的选择,有时候并不是不能折衷。