abstract 类 的 Rust 等价物是什么?
What is the Rust equivalent for abstract classes?
我仍然是 Rust 的初学者,我被困在一个关于特征的问题上。
尽管进行了多次尝试,但我找不到 rust 中抽象 类 的等价代码。
这是使用它们的 Typescript 代码示例:
export interface NodeConstructor<T> {
new (data: T): Node<T>;
}
export abstract class Node<T> {
public data: T;
public key: string;
public parentKeys: string[];
public childKeys: string[];
public parents: Node<T>[];
public children: Node<T>[];
constructor(data: T) {
this.data = data;
this.key = this.buildNodeKey();
this.parentKeys = this.buildParentKeys();
this.childKeys = this.buildChildKeys();
this.parents = [];
this.children = [];
}
get hasParents(): boolean {
return !!this.parents.length;
}
get hasChildren(): boolean {
return !!this.children.length;
}
abstract buildNodeKey(): string;
abstract buildChildKeys(): string[];
abstract buildParentKeys(): string[];
}
感谢 'mwlon' 中的解决方案 post,我得到了这个结果:
pub struct Node<T, BUILDER: ?Sized> where BUILDER: NodeBuilder {
pub data: T,
pub key: String,
pub parent_keys: Box<[String]>,
pub child_keys: Box<[String]>,
pub parents: Option<Box<[T]>>,
pub children: Option<Box<[T]>>,
builder: BUILDER,
}
pub trait NodeBuilder {
fn build_node_key(&self) -> String;
fn build_parent_key(&self) -> Box<[String]>;
fn build_child_key(&self) -> Box<[String]>;
}
impl<T , BUILDER> Node<T, BUILDER> where BUILDER: NodeBuilder {
pub fn new(&self, data: T) -> Node<T, BUILDER> {
Self{
data: data,
key: BUILDER::build_node_key(&self.builder),
parent_keys: BUILDER::build_parent_key(&self.builder),
child_keys: BUILDER::build_child_key(&self.builder),
parents: None,
children: None,
builder: self.builder
}
}
pub fn has_parents(&self) -> bool {
match &self.parents {
Some(_x) => true,
None => false,
}
}
pub fn has_children(&self) -> bool {
match &self.children {
Some(_x) => true,
None => false,
}
}
}
哪个实现是这样的:
struct TestModel {
name: String,
children: Option<Box<[String]>>,
parents: Option<Box<[String]>>
}
impl node::Node<TestModel, dyn node::NodeBuilder> {
fn build_child_key(data: TestModel) -> Box<[String]> {
match data.children {
Some(x) => x.clone(),
None => Box::new([]),
}
}
fn build_node_key(data: TestModel) -> String {
data.name.clone()
}
fn build_parent_key(data: TestModel) -> Box<[String]> {
match data.parents {
Some(x) => x.clone(),
None => Box::new([]),
}
}
}
但是我还有一个错误无法克服:
cannot move out of `self.builder` which is behind a shared reference
move occurs because `self.builder` has type `BUILDER`, which does not implement the `Copy` traitrustc(E0507)
node.rs(28, 22): move occurs because `self.builder` has type `BUILDER`, which does not implement the `Copy` trait
我无法在 Builder 上实现 'Copy',因为它也是一个特征。有什么我想念的吗?
Rust 中这种结构的最佳实践是什么?
我正在使用 rustc 1.59.0 (9d1b2106e 2022-02-23)
如果你真的想在 Rust 中复制抽象 classes,这是可能的。你走在正确的轨道上,有一个代表抽象方法的特征(NodeBuilder
)和一个通用的 struct
特征(Node
)。但是,所写示例存在一些问题。首先,你的构造函数。
Node::new
方法不应该采用&self
,因为它创建了一个新节点,它不会初始化已经存在的节点。与其他一些语言不同,Rust 没有对现有对象进行操作的构造函数。相反,对象创建是用类型上的自由函数表示的,通常称为 new
。一些其他语言调用这些函数 'static.'
- 为了创建一个节点,您将需要一个
BUILDER
的具体实例来存储在 self.builder
中。这可能应该是 Node::new
的一个参数。或者,如果您知道构建器本身永远不会携带状态,则可以将 NodeBuilder
特征上的方法设为静态并删除 builder
字段。
综合起来,这应该导致:
pub fn new(builder: BUILDER, data: T) -> Node<T, BUILDER> {
Self {
data: data,
key: builder.build_node_key(),
parent_keys: builder.build_parent_key(),
child_keys: builder.build_child_key(),
parents: None,
children: None,
builder: builder
}
}
其次,实施class。
- 该类型的所有'abstract'方法都封装在特征
NodeBuilder
中。因此,您不想在 node::Node<TestModel, dyn node::NodeBuilder>
上实现额外的关联函数,而是想在 TestModel
: 上实现 NodeBuilder
impl NodeBuilder for TestModel {
fn build_child_key(&self) -> Box<[String]> {
match self.children {
Some(x) => x.clone(),
None => Box::new([]),
}
}
fn build_node_key(&self) -> String {
self.name.clone()
}
fn build_parent_key(&self) -> Box<[String]> {
match self.parents {
Some(x) => x.clone(),
None => Box::new([]),
}
}
}
此时,可以将TestModel
的实例传递给Node::new
(连同一段数据)创建一个Node
,其抽象功能由[=实现26=].
一个抽象 class 将一段数据耦合在一起,该数据对于该类型的每个实例具有相同的形状,以及一组在该类型的不同实例之间可能不同的行为。这是 Rust 中一个相对不寻常的模式,因为 Rust 比其他语言更鼓励数据和行为的解耦。
您尝试做的事情的更地道的翻译可能是沿着这些路线。首先,我们创建一个类型,其中包含代表节点的 data:
pub struct Node<T> {
data: T,
key: String,
parent_keys: Box<[String]>,
child_keys: Box<[String]>,
parents: Option<Box<[T]>>,
children: Option<Box<[T]>>,
}
我们需要一种方法来创建这种类型的实例,因此我们将给它一个构造函数。我不知道您打算如何填充 parents
和 children
字段,所以我暂时将它们保留为 None
。但是,如果要使用 parent_keys
和 child_keys
从外部源读取数据来填充它们,则此构造函数可能是执行此操作的正确位置。
impl<T> Node<T> {
pub fn new(data: T, key: String, parent_keys: Box<[String]>, child_keys: Box<[String]>) -> Node<T> {
Node { data, key, parent_keys, child_keys, parents: None, children: None }
}
}
接下来,我们想要一个特征来抽象出可能的 行为 。在您的情况下,行为似乎是 'a way to create a node.' 特征应该具有实现其行为所必需的方法,因此:
pub trait NodeBuilder {
fn build_node<T>(&self, data: T) -> Node<T>;
}
我们可以在方法或结构中使用受 NodeBuilder
限制的泛型参数来抽象能够 构建节点的类型。我们可以通过为它们实现 NodeBuilder
来定义哪些类型能够做到这一点,就像这样:
struct TestModel {
name: String,
children: Option<Box<[String]>>,
parents: Option<Box<[String]>>
}
impl NodeBuilder for TestModel {
fn build_node<T>(&self, data: T) -> Node<T> {
let parents = self.parents.clone().unwrap_or_else(Default::default);
let children = self.children.clone().unwrap_or_else(Default::default);
Node::new(data, self.name.clone(), parents, children)
}
}
如您所见,此解决方案避免了在不必要时耦合数据和行为。但是,它特定于您所拥有的特定情况。不同的抽象 class 可能会转化为一组不同的类型和特征。这在跨编程范式转换时很常见:语言 A 中的一种模式的作用可能由语言 B 中的多种模式来填补,反之亦然。
与其过分关注如何在 Rust 中复制 'abstract classes' 这样的模式,您应该问问自己该模式在 TypeScript 中解决了什么问题,并考虑如何最好地解决 Rust 中的相同问题,即使这意味着使用不同的模式。
我仍然是 Rust 的初学者,我被困在一个关于特征的问题上。 尽管进行了多次尝试,但我找不到 rust 中抽象 类 的等价代码。 这是使用它们的 Typescript 代码示例:
export interface NodeConstructor<T> {
new (data: T): Node<T>;
}
export abstract class Node<T> {
public data: T;
public key: string;
public parentKeys: string[];
public childKeys: string[];
public parents: Node<T>[];
public children: Node<T>[];
constructor(data: T) {
this.data = data;
this.key = this.buildNodeKey();
this.parentKeys = this.buildParentKeys();
this.childKeys = this.buildChildKeys();
this.parents = [];
this.children = [];
}
get hasParents(): boolean {
return !!this.parents.length;
}
get hasChildren(): boolean {
return !!this.children.length;
}
abstract buildNodeKey(): string;
abstract buildChildKeys(): string[];
abstract buildParentKeys(): string[];
}
感谢 'mwlon' 中的解决方案 post,我得到了这个结果:
pub struct Node<T, BUILDER: ?Sized> where BUILDER: NodeBuilder {
pub data: T,
pub key: String,
pub parent_keys: Box<[String]>,
pub child_keys: Box<[String]>,
pub parents: Option<Box<[T]>>,
pub children: Option<Box<[T]>>,
builder: BUILDER,
}
pub trait NodeBuilder {
fn build_node_key(&self) -> String;
fn build_parent_key(&self) -> Box<[String]>;
fn build_child_key(&self) -> Box<[String]>;
}
impl<T , BUILDER> Node<T, BUILDER> where BUILDER: NodeBuilder {
pub fn new(&self, data: T) -> Node<T, BUILDER> {
Self{
data: data,
key: BUILDER::build_node_key(&self.builder),
parent_keys: BUILDER::build_parent_key(&self.builder),
child_keys: BUILDER::build_child_key(&self.builder),
parents: None,
children: None,
builder: self.builder
}
}
pub fn has_parents(&self) -> bool {
match &self.parents {
Some(_x) => true,
None => false,
}
}
pub fn has_children(&self) -> bool {
match &self.children {
Some(_x) => true,
None => false,
}
}
}
哪个实现是这样的:
struct TestModel {
name: String,
children: Option<Box<[String]>>,
parents: Option<Box<[String]>>
}
impl node::Node<TestModel, dyn node::NodeBuilder> {
fn build_child_key(data: TestModel) -> Box<[String]> {
match data.children {
Some(x) => x.clone(),
None => Box::new([]),
}
}
fn build_node_key(data: TestModel) -> String {
data.name.clone()
}
fn build_parent_key(data: TestModel) -> Box<[String]> {
match data.parents {
Some(x) => x.clone(),
None => Box::new([]),
}
}
}
但是我还有一个错误无法克服:
cannot move out of `self.builder` which is behind a shared reference
move occurs because `self.builder` has type `BUILDER`, which does not implement the `Copy` traitrustc(E0507)
node.rs(28, 22): move occurs because `self.builder` has type `BUILDER`, which does not implement the `Copy` trait
我无法在 Builder 上实现 'Copy',因为它也是一个特征。有什么我想念的吗? Rust 中这种结构的最佳实践是什么? 我正在使用 rustc 1.59.0 (9d1b2106e 2022-02-23)
如果你真的想在 Rust 中复制抽象 classes,这是可能的。你走在正确的轨道上,有一个代表抽象方法的特征(NodeBuilder
)和一个通用的 struct
特征(Node
)。但是,所写示例存在一些问题。首先,你的构造函数。
Node::new
方法不应该采用&self
,因为它创建了一个新节点,它不会初始化已经存在的节点。与其他一些语言不同,Rust 没有对现有对象进行操作的构造函数。相反,对象创建是用类型上的自由函数表示的,通常称为new
。一些其他语言调用这些函数 'static.'- 为了创建一个节点,您将需要一个
BUILDER
的具体实例来存储在self.builder
中。这可能应该是Node::new
的一个参数。或者,如果您知道构建器本身永远不会携带状态,则可以将NodeBuilder
特征上的方法设为静态并删除builder
字段。
综合起来,这应该导致:
pub fn new(builder: BUILDER, data: T) -> Node<T, BUILDER> {
Self {
data: data,
key: builder.build_node_key(),
parent_keys: builder.build_parent_key(),
child_keys: builder.build_child_key(),
parents: None,
children: None,
builder: builder
}
}
其次,实施class。
- 该类型的所有'abstract'方法都封装在特征
NodeBuilder
中。因此,您不想在node::Node<TestModel, dyn node::NodeBuilder>
上实现额外的关联函数,而是想在TestModel
: 上实现
NodeBuilder
impl NodeBuilder for TestModel {
fn build_child_key(&self) -> Box<[String]> {
match self.children {
Some(x) => x.clone(),
None => Box::new([]),
}
}
fn build_node_key(&self) -> String {
self.name.clone()
}
fn build_parent_key(&self) -> Box<[String]> {
match self.parents {
Some(x) => x.clone(),
None => Box::new([]),
}
}
}
此时,可以将TestModel
的实例传递给Node::new
(连同一段数据)创建一个Node
,其抽象功能由[=实现26=].
一个抽象 class 将一段数据耦合在一起,该数据对于该类型的每个实例具有相同的形状,以及一组在该类型的不同实例之间可能不同的行为。这是 Rust 中一个相对不寻常的模式,因为 Rust 比其他语言更鼓励数据和行为的解耦。
您尝试做的事情的更地道的翻译可能是沿着这些路线。首先,我们创建一个类型,其中包含代表节点的 data:
pub struct Node<T> {
data: T,
key: String,
parent_keys: Box<[String]>,
child_keys: Box<[String]>,
parents: Option<Box<[T]>>,
children: Option<Box<[T]>>,
}
我们需要一种方法来创建这种类型的实例,因此我们将给它一个构造函数。我不知道您打算如何填充 parents
和 children
字段,所以我暂时将它们保留为 None
。但是,如果要使用 parent_keys
和 child_keys
从外部源读取数据来填充它们,则此构造函数可能是执行此操作的正确位置。
impl<T> Node<T> {
pub fn new(data: T, key: String, parent_keys: Box<[String]>, child_keys: Box<[String]>) -> Node<T> {
Node { data, key, parent_keys, child_keys, parents: None, children: None }
}
}
接下来,我们想要一个特征来抽象出可能的 行为 。在您的情况下,行为似乎是 'a way to create a node.' 特征应该具有实现其行为所必需的方法,因此:
pub trait NodeBuilder {
fn build_node<T>(&self, data: T) -> Node<T>;
}
我们可以在方法或结构中使用受 NodeBuilder
限制的泛型参数来抽象能够 构建节点的类型。我们可以通过为它们实现 NodeBuilder
来定义哪些类型能够做到这一点,就像这样:
struct TestModel {
name: String,
children: Option<Box<[String]>>,
parents: Option<Box<[String]>>
}
impl NodeBuilder for TestModel {
fn build_node<T>(&self, data: T) -> Node<T> {
let parents = self.parents.clone().unwrap_or_else(Default::default);
let children = self.children.clone().unwrap_or_else(Default::default);
Node::new(data, self.name.clone(), parents, children)
}
}
如您所见,此解决方案避免了在不必要时耦合数据和行为。但是,它特定于您所拥有的特定情况。不同的抽象 class 可能会转化为一组不同的类型和特征。这在跨编程范式转换时很常见:语言 A 中的一种模式的作用可能由语言 B 中的多种模式来填补,反之亦然。
与其过分关注如何在 Rust 中复制 'abstract classes' 这样的模式,您应该问问自己该模式在 TypeScript 中解决了什么问题,并考虑如何最好地解决 Rust 中的相同问题,即使这意味着使用不同的模式。