我正在学习 Rust,我正在尝试实现一个通用类型的链表。到目前为止,我的 cons
和 len
函数可以工作,但是 map
有一些我无法弄清楚的问题。
use std::fmt;
#[derive(Debug)]
enum List<A> {
Empty,
Cons(A, Box<List<A>>),
}
fn cons<A>(x: A, xs: List<A>) -> List<A> {
return List::Cons(x, Box::new(xs));
}
fn len<A>(xs: List<A>) -> i32 {
match xs {
List::Empty => 0,
List::Cons(_, xs) => 1 + len(*xs),
}
}
fn map<A, B>(f: &Fn(A) -> B, xs: List<A>) -> List<B> {
match xs {
List::Empty => List::Empty,
List::Cons(x, xs) => cons(f(x), map(f, *xs)),
}
}
fn main() {
let xs = cons(1, cons(2, cons(3, List::Empty)));
println!("{:?}", xs);
println!("{:?}", len(xs));
let f = |x: i32| x * x;
println!("{:?})", map(f, xs));
}
错误
error[E0308]: mismatched types
--> src/main.rs:32:27
|
32 | println!("{:?})", map(f, xs));
| ^ expected reference, found closure
|
= note: expected type `&std::ops::Fn(_) -> _`
found type `[closure@src/main.rs:31:13: 31:27]`
预期输出
Cons(1, Cons(2, Cons(3, Empty)))
3
Cons(1, Cons(4, Cons(9, Empty)))
我的特殊问题在于
println!("{:?})", map(f, xs));
如果我注释掉那一行,输出的前两行是正确的。我不确定我的 map
调用有什么问题
更新
aochagavia 帮助我理解了函数引用问题和第一个所有权问题(显然是很多问题!)- 我在使用我们在 map< 中的
并得到一个新的错误len
中使用的相同技术时遇到了问题
我更新后的 map
函数如下所示
fn map<A, B>(f: &Fn(A) -> B, xs: &List<A>) -> List<B> {
match *xs {
List::Empty => List::Empty,
List::Cons(x, ref xs) => cons(f(x), map(f, xs)),
}
}
我正在尝试这个
let f = |x: i32| x * x;
let ys = map(&f, &xs);
let zs = map(&f, &xs);
println!("{:?})", ys);
println!("{:?})", zs);
新的错误是这样的
error[E0009]: cannot bind by-move and by-ref in the same pattern
--> src/main.rs:23:20
|
23 | List::Cons(x, ref xs) => cons(f(x), map(f, xs)),
| ^ ------ both by-ref and by-move used
| |
| by-move pattern here
最佳答案
错误消息很大,因为它发生在宏中,但如果您添加:let y = map(f, xs);
你会得到一个更短(并且稍微更准确)的:
error[E0308]: mismatched types
--> <anon>:32:15
|
32 | let y = map(f, xs);
| ^ expected reference, found closure
|
= note: expected type `&std::ops::Fn(_) -> _`
found type `[closure@<anon>:31:11: 31:25]`
也就是说,您是按值而不是按引用传递闭包!使用 map(&f, xs)
(注意&符号)应该可以解决错误。但是,还有另一个所有权问题(见下文)。
所有权问题
len
的类型签名功能是fn len<A> (xs: List<A>) -> i32
.这意味着它将获得列表的所有权以计算其长度。然而,这不是您想要的,因为它会阻止您以后使用该列表!因此,您会从编译器中得到错误。
解决这个问题的明智方法是让 len
借xs
而不是消费它。像这样:
fn len<A>(xs: &List<A>) -> i32 {
match *xs {
List::Empty => 0,
List::Cons(_, ref xs) => 1 + len(xs),
}
}
最后,您需要修改您的 main
通过调用 len
来反射(reflect)此更改的函数像这样:len(&xs)
(注意与号,您可以将其视为借用运算符)。
也让 map 借xs
正如 naomik 在评论中指出的那样,map
似乎也是借款的候选人xs
而不是消费它。一个可能的实现是:
fn map<A, B>(f: &Fn(&A) -> B, xs: &List<A>) -> List<B> {
match *xs {
List::Empty => List::Empty,
List::Cons(ref x, ref xs) => cons(f(x), map(f, xs)),
}
}
与原始版本的主要区别在于闭包现在采用 &A
而不是 A
(参见 Fn(&A) -> B
)。这是很自然的,因为不可能消耗包含在借用中的值(这意味着借用机制完全被破坏)。
在 main 中,您需要调用 map
像这样:
let f = |x: &i32| (*x) * (*x);
map(&f, &xs);
请注意 f
现在借用它的参数而不是使用它,正如 map
的类型签名所要求的那样.
闭包的一些额外背景
Rust 中的闭包有点特殊。你可以用一个很好的语法构造它们,但最终它们只是碰巧实现了 Fn
的结构。 , FnMut
或 FnOnce
特质。
如果您想按值传递它们(而不是像您在代码中那样按引用传递),您可以使用以下类型签名使 map 函数通用:
fn map<F, A, B> (f: F, xs: List<A>) -> List<B>
where F: Fn(A) -> B
{
这也为您提供了静态分派(dispatch)。如果您想了解更多信息,您可能应该阅读 trait objects and static/dynamic dispatch .
关于generics - 如何在 Rust 中实现通用链表映射函数?,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/43112482/