Iterate over slice and take ownership

To learn Mozilla Rust, I try to convert an existing .NET / C# application to Rust.

So far, I have created an enumeration which represents different PDF versions.

#[derive(PartialEq, Debug)]
pub enum PDFVersion {
    V14,
    V15,
    V16,
    V17,
    V20,
    V21,
}

And I also created a struct which represents a PDF Writer, which accepts a PDFVersion instance.

pub struct PDFWriter {
    version: PDFVersion,
}

// Provides the 'basic' implementation of the `PDFVersion` struct.
impl PDFWriter {
    pub fn new(version: PDFVersion) -> Self {
        PDFWriter { version }
    }
}

Now, I did want to create a couple of tests to ensure that PDFWriter::new results in a PDFWriter instance with the passed PDFVersion, so here are the tests:

#[cfg(test)]
mod tests {
    use super::*;

    #[test]
    fn test_new_pdfwriter() {
        [
            PDFVersion::V14,
            PDFVersion::V15,
            PDFVersion::V16,
            PDFVersion::V17,
            PDFVersion::V20,
            PDFVersion::V21,
        ]
        .iter()
        .for_each(|version| {
            // ACT.
            let result = PDFWriter::new(*version);

            // ASSERT.
            assert_eq!(result.version, *version);
        });
    }
}

At the moment, this test does NOT work since I need to dereference the pointer to version, but in order to do that, the PDFVersion enumeration should also derive from Copy and Clone.
I'm not sure if that's the good approach. I always wonder if adding those traits might impact the performance of the code.

Should I be concerned and "On Guard" when implementing this traits regarding performance?

First off, that shouldn't be the first thing to worry about. Of what use is such code that is "performant" but doesn't actually do what you need?

Other than that, such a trivial enum can, and probably should, in fact be Copy. It will be represented as a primitive integer anyway under the hood, so there's no point in not making it trivially copiable. (Unless of course you are assigning additional semantics to it that would require it to intentionally be move-only, but I find that unlikely in this case.)

To be clear: Copy is defined to be a bitwise memcpy by the language. There is literally no performance penalty in copying such types compared to moving them. And the compiler will prevent you from deriving or manually impl'ing the Copy trait for non-trivially-copiable types, so you never need to worry about this being an overhead.

The only case when you even need to think about cloning is when a type is only Clone but not Copy, because cloning can e.g. perform heap allocation.

2 Likes

Thanks for the explanation.
Since I'm just learning, what would be the "rust" way to solve this problem without implementing "Copy"?

The idiomatic way would be to implement Copy, as I explained above.

However, in this case, since you are creating the array (it is not a slice!), you have ownership of it, so you could simply call IntoIterator::into_iter([PDFVersion::V14, PDFVersion::V15]) instead of iter(), which consumes the array by-value and returns an iterator that also yields its elements by-value. This currently only works
on nightly.

I'm not sure if this is already possible. AFAIK, the into_iter call is edition-gated, so we have to use std::array::IntoIter::new.

4 Likes

From what I tested in my IDE, both iter and into_iter returns a PDFVersion instance, not a reference to a PDFVersion instance. However, inside the closure it becomes a reference, thus changing from iter to into_iter won't make any difference.

Minor terminology fix. There's no "Mozilla Rust" in the same sense as there's no "Bell C".

5 Likes

No, that is not the case. Nothing "becomes" a reference in the closure. The closure takes exactly the arguments it gets from the iterator. Unfortunately, the new behavior of .into_iter() seems not to be stable yet, but I provided a workaround for nightly in my amended post above. Here's a relevant Playground.

Thanks for the explanation.