about summary refs log tree commit diff
diff options
context:
space:
mode:
authorYuki Okushi <huyuumi.dev@gmail.com>2021-03-14 13:07:28 +0900
committerGitHub <noreply@github.com>2021-03-14 13:07:28 +0900
commit67bc866e5934e19c37fd7a115cdc8142fcb8c51d (patch)
tree866b0e116a0ac14b46c453443ed36e28ac65d1c4
parent6caa350503044420d4fd0cc324b3295a0c19cc70 (diff)
parent05ea200213fe30967fff38ebac887c52803aabd5 (diff)
downloadrust-67bc866e5934e19c37fd7a115cdc8142fcb8c51d.tar.gz
rust-67bc866e5934e19c37fd7a115cdc8142fcb8c51d.zip
Rollup merge of #82121 - lopopolo:pathbuf-osstring-extend, r=joshtriplett
Implement Extend and FromIterator for OsString

Add the following trait impls:

- `impl Extend<OsString> for OsString`
- `impl<'a> Extend<&'a OsStr> for OsString`
- `impl FromIterator<OsString> for OsString`
- `impl<'a> FromIterator<&'a OsStr> for OsString`

Because `OsString` is a platform string with no particular semantics, concatenating them together seems acceptable.

I came across a use case for these trait impls in https://github.com/artichoke/artichoke/pull/1089:

Artichoke is a Ruby interpreter. Its CLI accepts multiple `-e` switches for executing inline Ruby code, like:

```console
$ cargo -q run --bin artichoke -- -e '2.times {' -e 'puts "foo: #{__LINE__}"' -e '}'
foo: 2
foo: 2
```

I use `clap` for command line argument parsing, which collects these `-e` commands into a `Vec<OsString>`. To pass these commands to the interpreter for `Eval`, I need to join them together. Combining these impls with `Iterator::intersperse` https://github.com/rust-lang/rust/issues/79524 would enable me to build a single bit of Ruby code.

Currently, I'm doing something like:

```rust
let mut commands = commands.into_iter();
let mut buf = if let Some(command) = commands.next() {
    command
} else {
    return Ok(Ok(()));
};
for command in commands {
    buf.push("\n");
    buf.push(command);
}
```

If there's interest, I'd also like to add impls for `Cow<'a, OsStr>`, which would avoid allocating the `"\n"` `OsString` in the concatenate + intersperse use case.
-rw-r--r--library/std/src/ffi/os_str.rs86
1 files changed, 86 insertions, 0 deletions
diff --git a/library/std/src/ffi/os_str.rs b/library/std/src/ffi/os_str.rs
index 272eccda894..ce52ffc0241 100644
--- a/library/std/src/ffi/os_str.rs
+++ b/library/std/src/ffi/os_str.rs
@@ -5,6 +5,7 @@ use crate::borrow::{Borrow, Cow};
 use crate::cmp;
 use crate::fmt;
 use crate::hash::{Hash, Hasher};
+use crate::iter::{Extend, FromIterator};
 use crate::ops;
 use crate::rc::Rc;
 use crate::str::FromStr;
@@ -1192,3 +1193,88 @@ impl FromStr for OsString {
         Ok(OsString::from(s))
     }
 }
+
+#[stable(feature = "osstring_extend", since = "1.52.0")]
+impl Extend<OsString> for OsString {
+    #[inline]
+    fn extend<T: IntoIterator<Item = OsString>>(&mut self, iter: T) {
+        for s in iter {
+            self.push(&s);
+        }
+    }
+}
+
+#[stable(feature = "osstring_extend", since = "1.52.0")]
+impl<'a> Extend<&'a OsStr> for OsString {
+    #[inline]
+    fn extend<T: IntoIterator<Item = &'a OsStr>>(&mut self, iter: T) {
+        for s in iter {
+            self.push(s);
+        }
+    }
+}
+
+#[stable(feature = "osstring_extend", since = "1.52.0")]
+impl<'a> Extend<Cow<'a, OsStr>> for OsString {
+    #[inline]
+    fn extend<T: IntoIterator<Item = Cow<'a, OsStr>>>(&mut self, iter: T) {
+        for s in iter {
+            self.push(&s);
+        }
+    }
+}
+
+#[stable(feature = "osstring_extend", since = "1.52.0")]
+impl FromIterator<OsString> for OsString {
+    #[inline]
+    fn from_iter<I: IntoIterator<Item = OsString>>(iter: I) -> Self {
+        let mut iterator = iter.into_iter();
+
+        // Because we're iterating over `OsString`s, we can avoid at least
+        // one allocation by getting the first string from the iterator
+        // and appending to it all the subsequent strings.
+        match iterator.next() {
+            None => OsString::new(),
+            Some(mut buf) => {
+                buf.extend(iterator);
+                buf
+            }
+        }
+    }
+}
+
+#[stable(feature = "osstring_extend", since = "1.52.0")]
+impl<'a> FromIterator<&'a OsStr> for OsString {
+    #[inline]
+    fn from_iter<I: IntoIterator<Item = &'a OsStr>>(iter: I) -> Self {
+        let mut buf = Self::new();
+        for s in iter {
+            buf.push(s);
+        }
+        buf
+    }
+}
+
+#[stable(feature = "osstring_extend", since = "1.52.0")]
+impl<'a> FromIterator<Cow<'a, OsStr>> for OsString {
+    #[inline]
+    fn from_iter<I: IntoIterator<Item = Cow<'a, OsStr>>>(iter: I) -> Self {
+        let mut iterator = iter.into_iter();
+
+        // Because we're iterating over `OsString`s, we can avoid at least
+        // one allocation by getting the first owned string from the iterator
+        // and appending to it all the subsequent strings.
+        match iterator.next() {
+            None => OsString::new(),
+            Some(Cow::Owned(mut buf)) => {
+                buf.extend(iterator);
+                buf
+            }
+            Some(Cow::Borrowed(buf)) => {
+                let mut buf = OsString::from(buf);
+                buf.extend(iterator);
+                buf
+            }
+        }
+    }
+}