You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

如何在Rust中创建结构相同但类型不同的两个结构体?

Rust中实现结构相同但类型不同的物理量的优雅方案

作为物理学家,我在学习Rust时从粒子模拟器入手(此前用C++实现过),最初写出了这样的代码:

#[derive(Debug)]
struct PhysicalVector {
    x: f64,
    y: f64,
    z: f64,
}

impl PhysicalVector {
    fn add(&mut self, other: &PhysicalVector) {
        self.x = self.x + other.x;
        self.y = self.y + other.y;
        self.z = self.z + other.z;
    }
}

#[derive(Debug)]
struct Particle {
    position: PhysicalVector,
    velocity: PhysicalVector,
}

fn main() {
    let mut particle = Particle {
        position: PhysicalVector {x: 0., y: 0., z: 0.},
        velocity: PhysicalVector {x: 0., y: 0., z: 1.},
    };
    
    dbg!(&particle);
    
    particle.position.add(&PhysicalVector{x: 1., y: 0., z: 0.}); // 此操作合法!
    particle.position.add(&particle.velocity); // 这是错误的操作,不能将速度与位置相加!!
    
    dbg!(&particle);
}

这段代码可以运行,但用同一类型表示位置和速度会导致物理逻辑错误——比如particle.position.add(&particle.velocity)这种违反物理规律的操作无法被编译器阻止。

我尝试过「蛮力」方案:重复定义Position和Velocity结构体,但代码冗余且不优雅。

正确解决方案:利用泛型+零大小标记类型

Rust的类型系统可以通过**泛型结合零大小标记类型(ZST)**来实现结构相同但类型不同的物理量,既避免代码重复,又能在编译期阻止非法操作。

1. 定义零大小标记类型

这些类型仅用于区分物理量的语义,不占用任何内存:

#[derive(Debug)]
struct PositionTag;
#[derive(Debug)]
struct VelocityTag;

2. 定义泛型物理向量结构体

用标记类型作为泛型参数,通过PhantomData持有标记(不占内存):

use std::marker::PhantomData;

#[derive(Debug)]
struct PhysicalVector<T> {
    x: f64,
    y: f64,
    z: f64,
    _tag: PhantomData<T>, // 标记类型的占位符,无运行时开销
}

3. 实现通用方法

针对泛型PhysicalVector<T>实现通用操作(如加法),确保只有同标记类型的向量才能互相操作:

impl<T> PhysicalVector<T> {
    // 构造函数
    fn new(x: f64, y: f64, z: f64) -> Self {
        Self {
            x,
            y,
            z,
            _tag: PhantomData,
        }
    }

    // 同类型向量相加
    fn add(&mut self, other: &PhysicalVector<T>) {
        self.x += other.x;
        self.y += other.y;
        self.z += other.z;
    }
}

4. 定义类型别名提升可读性

为泛型结构体创建语义化的类型别名,让代码更直观:

type Position = PhysicalVector<PositionTag>;
type Velocity = PhysicalVector<VelocityTag>;

5. 最终使用示例

#[derive(Debug)]
struct Particle {
    position: Position,
    velocity: Velocity,
}

fn main() {
    let mut particle = Particle {
        position: Position::new(0., 0., 0.),
        velocity: Velocity::new(0., 0., 1.),
    };

    dbg!(&particle);

    // 合法操作:位置加位置(位移)
    particle.position.add(&Position::new(1., 0., 0.));

    // 非法操作:位置加速度,编译器直接报错,符合预期
    // particle.position.add(&particle.velocity);

    dbg!(&particle);
}

扩展:针对特定物理量实现专属方法

如果需要为不同物理量添加专属逻辑(比如速度乘以时间得到位移),可以针对特定标记类型单独实现:

impl Velocity {
    // 速度乘以时间得到位移(Position类型)
    fn multiply_by_time(&self, dt: f64) -> Position {
        Position::new(self.x * dt, self.y * dt, self.z * dt)
    }
}

此时可以合法执行符合物理逻辑的操作:

// 速度×时间得到位移,加到位置上
particle.position.add(&particle.velocity.multiply_by_time(1.0));

方案优势

  • 无重复代码:所有通用向量逻辑只需实现一次
  • 编译期安全:Rust类型系统直接阻止跨物理量的非法操作
  • 零额外开销:标记类型和PhantomData不占用内存,运行时效率与单一结构体完全一致

内容的提问来源于stack exchange,提问作者user171780

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.06.26 15:32:46