Follow

Keep Up to Date with the Most Important News

By pressing the Subscribe button, you confirm that you have read and are agreeing to our Privacy Policy and Terms of Use
Contact

Converting a generic Iterator (e.g. SkipWhile) back to a specialised iterator (e.g. std::path::Components)

To avoid the XY problem, here’s what I originally want to do:
I have a path: /foo/bar/bloo/baz/file.txt and would like the most efficient way to obtain bloo/baz/file.txt from that, essentially skipping all ancestors before a specific component (in this case "bloo"). The approach that seemed most sensible to me was to use the components function in std::path::Path.

My attempt looks like this:

let new_path = path
    .components()
    .skip_while(|v| if let Normal(v) = v { v.as_bytes() != b"bloo" } else { true })
    .skip(1)
    .as_path();

However this produces the following error:

MEDevel.com: Open-source for Healthcare and Education

Collecting and validating open-source software for healthcare, education, enterprise, development, medical imaging, medical records, and digital pathology.

Visit Medevel

error[E0599]: no method named `as_path` found for struct `Skip` in the current scope
   --> src/main.rs:712:14
    |
712 |             .as_path();
    |              ^^^^^^^ method not found in `Skip<SkipWhile<Components<'_>, [closure@src/main.rs:710:25: 710:89]>>`

For more information about this error, try `rustc --explain E0599`.
error: could not compile `bloggers` due to previous error

Which is pretty self explanatory. I could just collect the remaining components into a string using the underlying filesystem separator, but that seems like more of a workaround than the actual solution.

In a more general sense: Can we convert a generic iterator such as Skip or Map back to a specialised iterator such as Components, DirEntry, etc. as long as we can guarantee that the Item type is the same?

edit#1:
For reference, the most efficient solution I could think of to build a new path from a string looks as follows (using the unstable intersperse and MAIN_SEPARATOR_STR features):

let new_path: String = path
    .components()
    .skip_while(|v| if let Normal(v) = v { v.as_bytes() != b"bloo" } else { true })
    .skip(1)
    .map(|v| v.as_os_str().to_str())
    .intersperse(Some(std::path::MAIN_SEPARATOR_STR))
    .collect::<Option<_>>()
    .ok_or_else(|| /* error reporting of choice */)?;

>Solution :

The simplest way is to consume the iterator eagerly:

let mut new_path = path.components();
while let Some(v) = new_path.next() {
    if let Normal(v) = v {
        if v.as_bytes() == b"bloo" { // Can be just `v == "bloo"`
            break;
        }
    }
}
let new_path = new_path.as_path();

itertools has the dropping() method for an eager skip(), but doesn’t have an eager equivalent to skip_while(), unfortunately, so you have to roll your own, like I did.

Add a comment

Leave a Reply

Keep Up to Date with the Most Important News

By pressing the Subscribe button, you confirm that you have read and are agreeing to our Privacy Policy and Terms of Use

Discover more from Dev solutions

Subscribe now to keep reading and get access to the full archive.

Continue reading