【问题标题】:How to port C++ inheritance to Rust?如何将 C++ 继承移植到 Rust?
【发布时间】:2023-01-08 04:38:45
【问题描述】:

我正在尝试实现以下使用继承的 C++ 代码的 Rust 等效代码,但卡住了。这是我的示例代码:

class Vehicle {
public:
    double lat;
    double lon;
    double alt;

    double speed;
};
    
class CabVehicle : public Vehicle {
    
};
    
class PackerMoverVehicle : public Vehicle {
    
};
    
int main() {
    CabVehicle cv;
    cv.lat = 12.34;
    cv.lon = 12.34;
    cv.alt = 12.34;

    PackerMoverVehicle pmv;
    pmv.lat = 12.34;
    pmv.lon = 12.34;
    pmv.alt = 12.34;
}

这应该如何移植到 Rust?

【问题讨论】:

  • 生锈没有继承性。反正不是这样的。
  • 这是否回答了您的问题:stackoverflow.com/a/73163713/5397009
  • @SergioTulentsev 如何在 Rust 中实现相同的功能。我知道我可以保留两个单独的结构,即 CabVehiclePackerMoverVehicle 但我必须重复很多我觉得不好的字段
  • 当你在写 Rust 的时候用 C++ 思考时,感觉很尴尬。如果您可以解释您的 C++ 设计解决的一些特定问题,那么也许我们可以建议如何在惯用的 Rust 中解决该问题。但按原样,您的 C++ 层次结构什么也不做。
  • 通常聚合在重用代码方面几乎与继承一样好,同时更加灵活。看到这个简单的playground

标签: inheritance rust


【解决方案1】:

一般的答案是使用组合而不是继承。根据应用程序的不同,组合应该采用不同的方式。在大多数情况下,您应该从

struct VehicleState {
    lat: f64,
    lon: f64,
    alt: f64,
    speed: f64,
}

剩下的问题就是如何使用不同类型的车辆。


方式 1:如果代码的不同部分以不同的、不重叠的方式使用不同类型的车辆,您可以简单地在特定结构中包含状态结构:

struct Cab {
    state: VehicleState,
    // ... other fields
}

struct PackerMover {
    state: VehicleState,
    // ... other fields
}

这是最直接类似于 C++ 继承的版本,特别是在内存布局和静态类型方面。然而,这使得访问不同车辆的公共 state 变得很尴尬,并且它不支持动态调度,除非你编写一个带有访问 state 方法的特征(这对你的代码类型有一些限制)可以写)。通常应避免使用这种方法,除非您知道不需要其他任何东西。


方式 2:如果有代码应该对正在使用的车辆类型通用,但这是静态决定的,您可以创建一个通用结构:

struct Vehicle<T> {
    state: VehicleState,
    details: T,
}

struct Cab { /* ... */ }
struct PackerMover { /* ... */ }

/// This function only works with Cabs
fn foo(vehicle: Vehicle<Cab>) { /* ... */ } 
    
/// This function works with any Vehicle
fn foo<T>(vehicle: Vehicle<T>) { /* ... */ } 

这使得访问 state 变得容易,并且所有使用都是静态调度的。

如果你对 Vehicle 做一个小改动并添加一个特征,它也可以被动态调度:

struct Vehicle<T: ?Sized> { /* ... */
//              ^^^^^^^^ remove default restriction on the type parameter

trait VehicleDetails { /* add methods here */ }
impl VehicleDetails for Cab { /* ... */ }
impl VehicleDetails for PackerMover { /* ... */ }

这允许您将引用(或指针或Box&amp;Vehicle&lt;Cab&gt;强制转换为&amp;Vehicle&lt;dyn VehicleDetails&gt;,这是一个指针指向的类型任何Vehicle谁的T实现了VehicleDetails。例如,这可用于将各种车辆放入Vec&lt;Box&lt;Vehicle&lt;dyn VehicleDetails&gt;&gt;&gt;。使用 dyn 会导致通过 vtables 进行分派,就像 C++ 虚拟方法一样。

Info on this language feature。文档说“自定义 DST 目前基本上是一个半生不熟的功能”,但这种特殊情况正是它们的情况工作没有任何问题。)

如果您希望能够找出正在使用哪个“子类”并专门与之交互,这不是一个好的选择;如果车辆的所有特定特性都可以在 VehicleDetails 特性中表达,这是一个不错的选择。


方式 3:如果应用程序将要例行公事使用动态选择的车辆类型——特别是如果它经常想问“这辆车是Cab”然后与其Cabness 交互——那么你应该使用enum来包含详细信息。

struct Vehicle {
   state: VehicleState,
   kind: VehicleKind,
}

enum VehicleKind {
    Cab {
        seats: u16,
    },
    PackerMover {
        cargo_capacity: u64,
    }
}

这是动态调度,因为每个 Vehicle 都可以是任何类型,因此您始终可以混合和匹配车辆类型,但不涉及任何指针或 vtable。主要缺点是将其扩展到新种类需要修改单个enum VehicleKind,因此这不适合用户将使用 C++ 编写子类的库。但是,与我上面提到的 Vehicle&lt;dyn VehicleDetails&gt; 相比,使用它要简单得多。

【讨论】:

    【解决方案2】:

    Rust 基本上更像是一种过程式和函数式语言,具有一些伪 OO 特性,它同时比 C++ 更底层和更抽象(更接近 C 甚至 C-,但具有 ZCA 和更强的类型)。答案是:Rust 中没有继承,使用组合或者只是重写整个结构。这对你来说可能看起来很疯狂,但一段时间后你会明白没有必要继承。

    【讨论】:

      猜你喜欢
      • 2013-11-25
      • 1970-01-01
      • 1970-01-01
      • 2017-12-18
      • 1970-01-01
      • 1970-01-01
      • 2017-07-14
      • 1970-01-01
      • 1970-01-01
      相关资源
      最近更新 更多