【问题标题】:How to implement a generic linked list map function in Rust?如何在 Rust 中实现一个通用的链表映射函数?
【发布时间】:2017-03-30 08:32:27
【问题描述】:

我正在用 Rust 磨牙,我正在尝试实现一个通用类型的链表。到目前为止,我的 conslen 函数可以正常工作,但 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 帮助我理解了函数引用问题和第一个所有权问题(显然是很多问题!) - 我在使用 lenmap 中使用的相同技术时遇到了问题,并遇到了一个新错误

我更新后的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

【问题讨论】:

  • 我正在用 Rust 磨牙 => 祝你好运 :) 一开始可能需要一些工作来解决所有权/借款问题,但我保证前进变得更容易!当您遇到问题时,请不要犹豫仔细阅读 SO,如果您需要一些不适合此处的讨论/建议,您可以加入 IRC (chat.mibbit.com/…)、reddit (reddit.com/r/rust) 或用户论坛 ( users.rust-lang.org).
  • @MatthieuM.谢谢您的热烈欢迎。到目前为止,我发现 Rust 非常有趣,但是是的,其中一些事情真的让我陷入了困境。到目前为止,我使用过的任何语言都没有可比性(据我所知)。

标签: generics polymorphism rust


【解决方案1】:

错误消息很大,因为它发生在宏中,但如果您添加以下内容: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(&amp;f, xs)(注意与号)应该可以解决错误。但是,所有权还有另一个问题(见下文)。

所有权问题

len 函数的类型签名是fn len&lt;A&gt; (xs: List&lt;A&gt;) -&gt; i32。这意味着它将获取列表的所有权以计算其长度。然而,这不是您想要的,因为它会阻止您之后使用该列表!因此你从编译器得到的错误。

解决这个问题的明智方法是让len 借用xs 而不是消费它。像这样:

fn len<A>(xs: &List<A>) -> i32 {
    match *xs {
        List::Empty => 0,
        List::Cons(_, ref xs) => 1 + len(xs),
    }
}

最后,您需要修改您的 main 函数以通过调用 len 来反映此更改,如下所示:len(&amp;xs)(注意与符号,您可以将其视为借用运算符)。

让地图也借用 xs

正如 naomik 在 cmets 中指出的那样,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)),
    }
}

与原始版本的主要区别在于闭包现在采用&amp;A 而不是A(请参阅Fn(&amp;A) -&gt; B)。这是很自然的,因为不可能使用借用中包含的值(这意味着借用机制完全被破坏了)。

在 main 中,您需要像这样调用 map

let f = |x: &i32| (*x) * (*x);
map(&f, &xs);

请注意,f 现在借用其参数而不是使用它,这是 map 的类型签名所要求的。

闭包中的一些额外背景

闭包在 Rust 中有点特殊。你可以用很好的语法构造它们,但最终它们只是碰巧实现了FnFnMutFnOnce 特征的结构。

如果您想通过值传递它们(而不是通过引用,就像您在代码中所做的那样),您可以使用以下类型签名使 map 函数通用:

fn map<F, A, B> (f: F, xs: List<A>) -> List<B>
    where F: Fn(A) -> B
{

这也为您提供静态调度。如果您想了解更多信息,您应该阅读trait objects and static/dynamic dispatch

【讨论】:

  • 好的,谢谢!我看到了与所有权相关的新问题,但我不确定它是否需要一个完全独立的问题,因为仅此答案不会产生我的预期输出。您认为您也可以在这里帮助我解决所有权问题吗?
  • 有趣...好吧,但我不想让我的map 函数borrow xs 吗?例如,如果我想映射 xs 两次,它看起来像第一次调用映射 moves xs - 当我不是时必须更明确地使用借用运算符似乎很奇怪改变我的函数中的值。我知道我的代码在几乎每个参数之前都会有&amp; - 同样在每个参数之前都有&amp;......这看起来对吗?
  • 视情况而定。在这种情况下,它似乎是合理的,所以你一定要尝试一下。如果您需要帮助,请随时在此处发表评论。
  • 更新了我的问题,以证明我无法接受您在len 中教给我的有关借款的知识并将其应用于map :(
  • 哎呀,似乎我还有很长的路要走才能开始以任何方式变得自然。现在,当函数的 default 行为是“使用”一个参数时,我的直觉反应是混乱。似乎应该像 mut 那样明确地调用它,但默认情况下会使用 not。我想知道学习 Rust 习语会如何随着时间的推移改变这种观点......无论如何,谢谢你如此彻底地处理我的问题。我真的很感激。
猜你喜欢
  • 2021-11-21
  • 1970-01-01
  • 2019-05-15
  • 2011-05-02
  • 2019-05-02
  • 2018-04-03
  • 2012-01-04
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多