简体   繁体   English

Rust:如何将 `iter().map()` 或 `iter().enumarate()` 分配给同一个变量

[英]Rust: how to assign `iter().map()` or `iter().enumarate()` to same variable

struct A {...whatever...};
const MY_CONST_USIZE:usize = 127;


// somewhere in function

// vec1_of_A:Vec<A> vec2_of_A_refs:Vec<&A> have values from different data sources and have different inside_item types
let my_iterator;
if my_rand_condition() { // my_rand_condition is random and compiles for sake of simplicity
    my_iterator = vec1_of_A.iter().map(|x| (MY_CONST_USIZE, &x)); // Map<Iter<Vec<A>>>
} else {
    my_iterator = vec2_of_A_refs.iter().enumerate(); // Enumerate<Iter<Vec<&A>>>
}

how to make this code compile?如何使这段代码编译?

at the end (based on condition) I would like to have iterator able build from both inputs and I don't know how to integrate these Map and Enumerate types into single variable without calling collect() to materialize iterator as Vec or maybe it's against functional programming principles最后(基于条件)我希望能够从两个输入构建迭代器,但我不知道如何将这些MapEnumerate类型集成到单个变量中,而不调用collect()将迭代器具体化为Vec或者它可能是反对的函数式编程原则

reading material will be welcomed欢迎阅读材料

In the vec_of_A case, first you need to replace &x with x in your map function.vec_of_A的情况下,首先您需要在 map function 中将&x替换为x The code you have will never compile because the mapping closure tries to return a reference to one of its parameters, which is never allowed in Rust.您拥有的代码永远不会编译,因为映射闭包试图返回对其参数之一的引用,这在 Rust 中是不允许的。 To make the types match up, you need to dereference the &&A in the vec2_of_A_refs case to &A instead of trying to add a reference to the other.为了使类型匹配,您需要将vec2_of_A_refs案例中的&&A取消引用到&A而不是尝试添加对另一个的引用。

Also, -127 is an invalid value for usize , so you need to pick a valid value, or use a different type than usize .此外, -127usize的无效值,因此您需要选择一个有效值,或使用与usize不同的类型。

Having fixed those, now you need some type of dynamic dispatch.修复了这些,现在您需要某种类型的动态调度。 The simplest approach would be boxing into a Box<dyn Iterator> .最简单的方法是装箱成Box<dyn Iterator>

Here is a complete example:这是一个完整的例子:

#![allow(unused)]
#![allow(non_snake_case)]

struct A;
// Fixed to be a valid usize.
const MY_CONST_USIZE: usize = usize::MAX;

fn my_rand_condition() -> bool { todo!(); }

fn example() {
    let vec1_of_A: Vec<A> = vec![];
    let vec2_of_A_refs: Vec<&A> = vec![];
    
    let my_iterator: Box<dyn Iterator<Item=(usize, &A)>>;
    if my_rand_condition() {
        // Fixed to return x instead of &x
        my_iterator = Box::new(vec1_of_A.iter().map(|x| (MY_CONST_USIZE, x)));
    } else {
        // Added map to deref &&A to &A to make the types match
        my_iterator = Box::new(vec2_of_A_refs.iter().map(|x| *x).enumerate());
    }
    
    for item in my_iterator {
        // ...
    }
}

( Playground ) 游乐场

Instead of a boxed trait object, you could also use the Either type from the either crate.除了盒装特征 object,您还可以使用either一板条箱中的Either类型。 This is an enum with Left and Right variants, but the Either type itself implements Iterator if both the left and right types also do, with the same type for the Item associated type.这是一个具有LeftRight变体的枚举,但是如果 left 和 right 类型也都这样做,则Either类型本身实现Iterator ,并且Item关联类型具有相同的类型。 For example:例如:

#![allow(unused)]
#![allow(non_snake_case)]

use either::Either;

struct A;
const MY_CONST_USIZE: usize = usize::MAX;

fn my_rand_condition() -> bool { todo!(); }

fn example() {
    let vec1_of_A: Vec<A> = vec![];
    let vec2_of_A_refs: Vec<&A> = vec![];
    
    let my_iterator;
    if my_rand_condition() {
        my_iterator = Either::Left(vec1_of_A.iter().map(|x| (MY_CONST_USIZE, x)));
    } else {
        my_iterator = Either::Right(vec2_of_A_refs.iter().map(|x| *x).enumerate());
    }
    
    for item in my_iterator {
        // ...
    }
}

( Playground ) 游乐场

Why would you choose one approach over the other?为什么你会选择一种方法而不是另一种?

Pros of the Either approach: Either方法的优点:

  • It does not require a heap allocation to store the iterator.它不需要堆分配来存储迭代器。
  • It implements dynamic dispatch via match which is likely (but not guaranteed) to be faster than dynamic dispatch via vtable lookup.它通过match实现动态调度,这可能(但不保证)比通过 vtable 查找的动态调度更快。

Pros of the boxed trait object approach:盒装特征 object 方法的优点:

  • It does not depend on any external crates.它不依赖于任何外部 crate。
  • It scales easily to many different types of iterators;它很容易扩展到许多不同类型的迭代器; the Either approach quickly becomes unwieldy with more than two types.如果有两种以上的类型, Either方法很快就会变得笨拙。

You can do this using a Box ed trait object like so:您可以使用Box ed trait object 来执行此操作,如下所示:

let my_iterator: Box<dyn Iterator<Item = _>> = if my_rand_condition() {
    Box::new(vec1_of_A.iter().map(|x| (MY_CONST_USIZE, x)))
} else {
    Box::new(vec2_of_A_refs.iter().enumerate().map(|(i, x)| (i, *x)))
};

I don't think this is a good idea generally though.不过,我认为这通常不是一个好主意。 A few things to note:需要注意的几点:

  • The use of trait objects means the types here must be resolved dynamically.使用 trait 对象意味着这里的类型必须动态解析。 This adds a lot of overhead.这增加了很多开销。
  • The closure in vec1 's iterator's map method cannot reference its arguments. vec1的迭代器的map方法中的闭包无法引用其 arguments。 Instead the second map must be added to vec2 s iterator.相反,必须将第二个map添加到vec2的迭代器中。 The effect of this is that all the items are being copied regardless.这样做的效果是无论如何都将复制所有项目。 If you are doing this, why not collect() ?如果你这样做,为什么不collect() The overhead for creating the Vec or whatever you choose should be less than that of the dynamic resolution.创建Vec或您选择的任何内容的开销应小于动态分辨率的开销。
  • Bit pedantic, but remember if statements are expressions in Rust, and so the assignment can be expressed a little more cleanly as I have done above.有点迂腐,但请记住if语句是 Rust 中的表达式,因此可以像我上面所做的那样更清晰地表达赋值。

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM