How to execute a method on a struct for each instance in a collection of structs until an error is encountered in Rust?

huangapple go评论91阅读模式
英文:

How to execute a method on a struct for each instance in a collection of structs until an error is encountered in Rust?

问题

我正在尝试运行LogCommand结构体的每个实例的execute方法,当遇到错误时停止。

use std::pin::Pin;
use std::future::Future;

use anyhow::Error;
use futures::future::ok;
use futures::stream::{TryStream, TryStreamExt};

pub struct LogCommand {
    message: String,
}

impl LogCommand {
    fn new(message: String) -> LogCommand {
        LogCommand { message }
    }

    fn execute(&self) -> Pin<Box<dyn Future<Output = Result<bool, Error>>>> {
        println!("{}", self.message);
        Box::pin(ok(true))
    }
}

fn main() {
    let mut structs: Vec<LogCommand> = Vec::new();
    structs.push(LogCommand::new("Test1".to_string()));
    structs.push(LogCommand::new("Test2".to_string()));
    structs.push(LogCommand::new("Test3".to_string()));

    run_all_methods(structs);
}

async fn run_all_methods(logCommands: Vec<LogCommand>) -> Result<bool, Error> {
    futures::stream::iter(logCommands).try_for_each(|i| i.execute()).await
}

我遇到的错误如下:

the method `try_for_each` exists for struct `Iter<IntoIter<LogCommand>>`, but its trait bounds were not satisfied
the following trait bounds were not satisfied:
`futures::stream::Iter<std::vec::IntoIter<LogCommand>>: TryStream`
which is required by `futures::stream::Iter<std::vec::IntoIter<LogCommand>>: TryStreamExt`
`&futures::stream::Iter<std::vec::IntoIter<LogCommand>>: TryStream`
which is required by `&futures::stream::Iter<std::vec::IntoIter<LogCommand>>: TryStreamExt`
`&mut futures::stream::Iter<std::vec::IntoIter<LogCommand>>: TryStream`
which is required by `&mut futures::stream::Iter<std::vec::IntoIter<LogCommand>>: TryStreamExt`rustcClick for full compiler diagnostic
iter.rs(9, 1): doesn't satisfy `_: TryStreamExt`
iter.rs(9, 1): doesn't satisfy `_: TryStream`

你有什么想法,我如何运行每个方法并在遇到错误时停止,而不直接使用for循环?(我被要求以更函数式的方式编写这段代码。)

英文:

I am trying to run the execute method for each instance of the struct LogCommand that I have in a collection of structs, stopping when an error is encountered.

use std::pin::Pin;
use std::future::Future;

use anyhow::Error;
use futures::future::ok;
use futures::stream::{TryStream, TryStreamExt};

pub struct LogCommand {
    message: String,
}

impl LogCommand {
    fn new(message: String) -&gt; LogCommand {
        LogCommand { message }
    }

    fn execute(&amp;self) -&gt; Pin&lt;Box&lt;dyn Future&lt;Output = Result&lt;bool, Error&gt;&gt;&gt;&gt; {
        println!(&quot;{}&quot;, self.message);
        Box::pin(ok(true))
    }
}

fn main() {
    let mut structs: Vec&lt;LogCommand&gt; = Vec::new();
    structs.push(LogCommand::new(&quot;Test1&quot;.to_string()));
    structs.push(LogCommand::new(&quot;Test2&quot;.to_string()));
    structs.push(LogCommand::new(&quot;Test3&quot;.to_string()));

    run_all_methods(structs);
}

async fn run_all_methods(logCommands: Vec&lt;LogCommand&gt;) -&gt; Result&lt;bool, Error&gt; {
    futures::stream::iter(logCommands).try_for_each(|i| i.execute())
}

The error I am getting is as follows:

the method `try_for_each` exists for struct `Iter&lt;IntoIter&lt;LogCommand&gt;&gt;`, but its trait bounds were not satisfied
the following trait bounds were not satisfied:
`futures::stream::Iter&lt;std::vec::IntoIter&lt;LogCommand&gt;&gt;: TryStream`
which is required by `futures::stream::Iter&lt;std::vec::IntoIter&lt;LogCommand&gt;&gt;: TryStreamExt`
`&amp;futures::stream::Iter&lt;std::vec::IntoIter&lt;LogCommand&gt;&gt;: TryStream`
which is required by `&amp;futures::stream::Iter&lt;std::vec::IntoIter&lt;LogCommand&gt;&gt;: TryStreamExt`
`&amp;mut futures::stream::Iter&lt;std::vec::IntoIter&lt;LogCommand&gt;&gt;: TryStream`
which is required by `&amp;mut futures::stream::Iter&lt;std::vec::IntoIter&lt;LogCommand&gt;&gt;: TryStreamExt`rustcClick for full compiler diagnostic
iter.rs(9, 1): doesn&#39;t satisfy `_: TryStreamExt`
iter.rs(9, 1): doesn&#39;t satisfy `_: TryStream`

Any idea on how I might go about running each of these methods and stopping when an error is encountered, without directly using a for loop? (I have been asked to write this in a more functional manner.)

答案1

得分: 3

错误的原因是try_for_each()TryStream的方法,而futures::stream::iter(logCommands)不是Result的流,而是LogCommand的流。Result还没有出现,因为execute()还没有完成。

要获取Result的流,可以使用StreamExt::then(),然后使用其中一个TryStream操作来获取完成或错误。

但是你没有说明你想要对这些bool做什么。如果你实际上不想要任何数据,可以将其替换为()并进行收集:

use futures::StreamExt;

...

impl LogCommand {
    fn new(message: String) -> LogCommand {
        LogCommand { message }
    }

    fn execute(&self) -> Pin<Box<dyn Future<Output = Result<(), Error>>>> {
        println!("{}", self.message);
        Box::pin(ok(()))
    }
}

async fn run_all_methods(log_commands: Vec<LogCommand>) -> Result<(), Error> {
    futures::stream::iter(log_commands)
        .then(|i| i.execute())
        .try_collect()
        .await
}

Playground

如果你想以某种方式组合这些bool(逻辑与或逻辑或),那么也许try_fold()会适合。如果你想要在bool具有特定值时提前退出,将其转换为错误。

英文:

The reason for the error is that try_for_each() is a method for TryStreams — streams of Results — but futures::stream::iter(logCommands) is not a stream of Results but of LogCommands. The Results haven't appeared yet because execute() hasn't been done yet.

To get the stream of Results, you can use StreamExt::then(), and then to get the completion or error you can use one of the TryStream operations.

But you haven't said what you want to do with the bools. If you don't actually want any data there, you can replace it with () and collect:

use futures::StreamExt;

...

impl LogCommand {
    fn new(message: String) -&gt; LogCommand {
        LogCommand { message }
    }

    fn execute(&amp;self) -&gt; Pin&lt;Box&lt;dyn Future&lt;Output = Result&lt;(), Error&gt;&gt;&gt;&gt; {
        println!(&quot;{}&quot;, self.message);
        Box::pin(ok(()))
    }
}

async fn run_all_methods(log_commands: Vec&lt;LogCommand&gt;) -&gt; Result&lt;(), Error&gt; {
    futures::stream::iter(log_commands)
        .then(|i| i.execute())
        .try_collect()
        .await
}

Playground

If you want to combine the bools in some way (logical AND or OR), then maybe try_fold() will suit. If you want to exit early if the bool has a particular value, convert it to an error.

答案2

得分: 1

你可以阅读Kevin Reids的答案来了解为什么你的代码不能正常工作。但是既然我已经在看到那个答案之前编写了代码,我也会为run_all_methods添加我的方法:

async fn run_all_methods(logCommands: Vec<LogCommand>) -> Result<bool, Error> {
    logCommands
        .iter()
        .map(|c| c.execute())
        .collect::<FuturesUnordered<_>>()
        .try_fold(false, |acc, b| ok(acc || b))
        .await
}

Playground

我选择了在尽可能长时间内保持在Iterator领域而不是立即创建一个Stream的选项,因为我认为它们比流更常用,所以编译器更有可能对其进行优化,并且你可以获得为它们实现的所有功能(尽管大多数在Stream中都有复制,但该API感觉不太完善和完整)。

在将结构体映射为Futures之后,我将其收集到FuturesUnordered中,不过根据需求,它也可以是FuturesOrdered,这样我就可以对从Stream中产生的值有更多的控制,而不仅仅是使用stream::iter

由于你没有指定你想要对所有的布尔值做什么,我选择了对它们进行逻辑或操作。对于一个集合,你可以用try_collect替换try_fold,并将返回类型中的bool替换为Vec<bool>,对于早期返回,你可以在折叠中返回Err变体而不是Ok

英文:

You can read why your code doesn't work as is in Kevin Reids answer. But since I had already written the code when I saw that I'll add my approach for run_all_methods as well:

async fn run_all_methods(logCommands: Vec&lt;LogCommand&gt;) -&gt; Result&lt;bool, Error&gt; {
    logCommands
        .iter()
        .map(|c| c.execute())
        .collect::&lt;FuturesUnordered&lt;_&gt;&gt;()
        .try_fold(false, |acc, b| ok(acc || b))
        .await
}

Playground

Instead of immediately creating a Stream I went for the option of staying in Iterator land as long as possible, since they're used quite more frequently than streams I believe that route is more likely to be optimized for in the compiler and you get all the bells and whistles that are implemented for them (granted most are replicated in Streams but that API doesn't feel quite as polished & complete)

After mapping the structs to Futures I collect into a FuturesUnordered though depending on the needs it might as well have been a FuturesOrdered giving me a little more clontrol over how values are produced from the Stream than the stream::iter gives.

Since you haven't specified what you want to do with all the bools I've opted for a disjunction of them. For a collection you'd replace the try_fold with try_collect and switch bool with Vec&lt;bool&gt; in the return type, for an early return you'd return an Err variant instead of Ok in the fold.

huangapple
  • 本文由 发表于 2023年8月9日 02:11:00
  • 转载请务必保留本文链接:https://go.coder-hub.com/76862185.html
匿名

发表评论

匿名网友

:?: :razz: :sad: :evil: :!: :smile: :oops: :grin: :eek: :shock: :???: :cool: :lol: :mad: :twisted: :roll: :wink: :idea: :arrow: :neutral: :cry: :mrgreen:

确定