Skip to content

Commit 0cc5035

Browse files
authored
Rollup merge of rust-lang#62528 - SimonSapin:concat, r=alexcrichton
Add joining slices of slices with a slice separator, not just a single item rust-lang#27747 (comment) > It's kinda annoying to be able to join strings with a str (which can have multiple chars), but joining a slice of slices, you can only join with a single element. This turns out to be fixable, with some possible inference regressions. # TL;DR Related trait(s) are unstable and tracked at rust-lang#27747, but the `[T]::join` method that is being extended here is already stable. Example use of the new insta-stable functionality: ```rust let nested: Vec<Vec<Foo>> = /* … */; let separator: &[Foo] = /* … */; // Previously: could only be a single &Foo nested.join(separator) ``` Complete API affected by this PR, after changes: ```rust impl<T> [T] { pub fn concat<Item: ?Sized>(&self) -> <Self as Concat<Item>>::Output where Self: Concat<Item> { Concat::concat(self) } pub fn join<Separator>(&self, sep: Separator) -> <Self as Join<Separator>>::Output where Self: Join<Separator> { Join::join(self, sep) } } // The `Item` parameter is only useful for the the slice-of-slices impl. pub trait Concat<Item: ?Sized> { type Output; fn concat(slice: &Self) -> Self::Output; } pub trait Join<Separator> { type Output; fn join(slice: &Self, sep: Separator) -> Self::Output; } impl<T: Clone, V: Borrow<[T]>> Concat<T> for [V] { type Output = Vec<T>; } impl<T: Clone, V: Borrow<[T]>> Join<&'_ T> for [V] { type Output = Vec<T>; } // New functionality here! impl<T: Clone, V: Borrow<[T]>> Join<&'_ [T]> for [V] { type Output = Vec<T>; } impl<S: Borrow<str>> Concat<str> for [S] { type Output = String; } impl<S: Borrow<str>> Join<&'_ str> for [S] { type Output = String; } ``` # Details After rust-lang#62403 but before this PR, the API is: ```rust impl<T> [T] { pub fn concat<Separator: ?Sized>(&self) -> T::Output where T: SliceConcat<Separator> { SliceConcat::concat(self) } pub fn join<Separator: ?Sized>(&self, sep: &Separator) -> T::Output where T: SliceConcat<Separator> { SliceConcat::join(self, sep) } } pub trait SliceConcat<Separator: ?Sized>: Sized { type Output; fn concat(slice: &[Self]) -> Self::Output; fn join(slice: &[Self], sep: &Separator) -> Self::Output; } impl<T: Clone, V: Borrow<[T]>> SliceConcat<T> for V { type Output = Vec<T>; } impl<S: Borrow<str>> SliceConcat<str> for S { type Output = String; } ``` By adding a trait impl we should be able to accept a slice of `T` as the separator, as an alternative to a single `T` value. In a `some_slice.join(some_separator)` call, trait resolution will pick an impl or the other based on the type of `some_separator`. In `some_slice.concat()` however there is no separator, so this call would become ambiguous. Some regression in type inference or trait resolution may be acceptable on principle, but requiring a turbofish for every single call to `concat` isn’t great. The solution to that is splitting the `SliceConcat` trait into two `Concat` and `Join` traits, one for each eponymous method. Only `Join` would gain a new impl, so that `some_slice.concat()` would not become ambiguous. Now, at the trait level the `Concat` trait does not need a `Separator` parameter anymore. However, simply removing it causes one of the impls not to be accepted anymore: ```rust error[E0207]: the type parameter `T` is not constrained by the impl trait, self type, or predicates --> src/liballoc/slice.rs:608:6 | 608 | impl<T: Clone, V: Borrow<[T]>> Concat for [V] { | ^ unconstrained type parameter ``` This makes sense: if `[V]::concat` is a method that is itself not generic, then its return type (which is the `Concat::Output` associated type) needs to be determined based on solely `V`. And although there is no such type in the standard library, there is nothing stopping another crate from defining a `V` type that implements both `Borrow<[Foo]>` and `Borrow<[Bar]>`. It might not be a good idea, but it’s possible. Both would apply here, and there would be no way to determine `T`. This could be a warning sign that this API is too generic. Perhaps we’d be better off having one less type variable, and only implement `Concat for [&'_ [T]]` and `Concat for [Vec<T>]` etc. However this aspect of `[V]::concat` is already stable, so we’re stuck with it. The solution is to keep a dummy type parameter on the `Concat` trait. That way, if a type has multiple `Borrow<[_]>` impls, it’ll end up with multiple corresponding `Concat<_>` impls. In `impl<S: Borrow<str>> Concat<str> for [S]`, the second occurrence of `str` is not meaningful. It could be any type. As long as there is only once such type with an applicable impl, trait resolution will be appeased without demanding turbofishes. # Joining strings with `char` For symmetry I also tried adding this impl (because why not): ```rust impl<S: Borrow<str>> Join<char> for [S] { type Output = String; } ``` This immediately caused an inference regression in a dependency of rustc: ```rust error[E0277]: the trait bound `std::string::String: std::borrow::Borrow<[std::string::String]>` is not satisfied --> /home/simon/.cargo/registry/src/github.com-1ecc6299db9ec823/getopts-0.2.19/src/lib.rs:595:37 | 595 | row.push_str(&desc_rows.join(&desc_sep)); | ^^^^ the trait `std::borrow::Borrow<[std::string::String]>` is not implemented for `std::string::String` | = help: the following implementations were found: <std::string::String as std::borrow::Borrow<str>> = note: required because of the requirements on the impl of `std::slice::Join<&std::string::String>` for `[std::string::String]` ``` In the context of this code, two facts are known: * `desc_rows` is a `Vec<String>` * `desc_sep` is a `String` Previously the first fact alone reduces the resolution of `join` to only one solution, where its argument it expected to be `&str`. Then, `&String` is coerced to `&str`. With the new `Join` impl, the first fact leavs two applicable impls where the separator can be either `&str` or `char`. But `&String` is neither of these things. It appears that possible coercions are not accounted for, in the search for a solution in trait resolution. I have not included this new impl in this PR. It’s still possible to add later, but the `getopts` breakage does not need to block the rest of the PR. And the functionality easy for end-user to duplicate: `slice_of_strings.join(&*char_separator.encode_utf8(&mut [0_u8, 4]))` The `&*` part of that last code snippet is another case of the same issue: `encode_utf8` returns `&mut str` which can be coerced to `&str`, but isn’t when trait resolution is ambiguous.
2 parents 57d2b28 + 5f7768a commit 0cc5035

File tree

2 files changed

+96
-29
lines changed

2 files changed

+96
-29
lines changed

src/liballoc/slice.rs

+83-21
Original file line numberDiff line numberDiff line change
@@ -494,10 +494,10 @@ impl<T> [T] {
494494
/// assert_eq!([[1, 2], [3, 4]].concat(), [1, 2, 3, 4]);
495495
/// ```
496496
#[stable(feature = "rust1", since = "1.0.0")]
497-
pub fn concat<Separator: ?Sized>(&self) -> T::Output
498-
where T: SliceConcat<Separator>
497+
pub fn concat<Item: ?Sized>(&self) -> <Self as Concat<Item>>::Output
498+
where Self: Concat<Item>
499499
{
500-
SliceConcat::concat(self)
500+
Concat::concat(self)
501501
}
502502

503503
/// Flattens a slice of `T` into a single value `Self::Output`, placing a
@@ -508,12 +508,13 @@ impl<T> [T] {
508508
/// ```
509509
/// assert_eq!(["hello", "world"].join(" "), "hello world");
510510
/// assert_eq!([[1, 2], [3, 4]].join(&0), [1, 2, 0, 3, 4]);
511+
/// assert_eq!([[1, 2], [3, 4]].join(&[0, 0][..]), [1, 2, 0, 0, 3, 4]);
511512
/// ```
512513
#[stable(feature = "rename_connect_to_join", since = "1.3.0")]
513-
pub fn join<Separator: ?Sized>(&self, sep: &Separator) -> T::Output
514-
where T: SliceConcat<Separator>
514+
pub fn join<Separator>(&self, sep: Separator) -> <Self as Join<Separator>>::Output
515+
where Self: Join<Separator>
515516
{
516-
SliceConcat::join(self, sep)
517+
Join::join(self, sep)
517518
}
518519

519520
/// Flattens a slice of `T` into a single value `Self::Output`, placing a
@@ -528,10 +529,10 @@ impl<T> [T] {
528529
/// ```
529530
#[stable(feature = "rust1", since = "1.0.0")]
530531
#[rustc_deprecated(since = "1.3.0", reason = "renamed to join")]
531-
pub fn connect<Separator: ?Sized>(&self, sep: &Separator) -> T::Output
532-
where T: SliceConcat<Separator>
532+
pub fn connect<Separator>(&self, sep: Separator) -> <Self as Join<Separator>>::Output
533+
where Self: Join<Separator>
533534
{
534-
SliceConcat::join(self, sep)
535+
Join::join(self, sep)
535536
}
536537

537538
}
@@ -578,45 +579,83 @@ impl [u8] {
578579
// Extension traits for slices over specific kinds of data
579580
////////////////////////////////////////////////////////////////////////////////
580581

581-
/// Helper trait for [`[T]::concat`](../../std/primitive.slice.html#method.concat)
582-
/// and [`[T]::join`](../../std/primitive.slice.html#method.join)
582+
/// Helper trait for [`[T]::concat`](../../std/primitive.slice.html#method.concat).
583+
///
584+
/// Note: the `Item` type parameter is not used in this trait,
585+
/// but it allows impls to be more generic.
586+
/// Without it, we get this error:
587+
///
588+
/// ```error
589+
/// error[E0207]: the type parameter `T` is not constrained by the impl trait, self type, or predica
590+
/// --> src/liballoc/slice.rs:608:6
591+
/// |
592+
/// 608 | impl<T: Clone, V: Borrow<[T]>> Concat for [V] {
593+
/// | ^ unconstrained type parameter
594+
/// ```
595+
///
596+
/// This is because there could exist `V` types with multiple `Borrow<[_]>` impls,
597+
/// such that multiple `T` types would apply:
598+
///
599+
/// ```
600+
/// # #[allow(dead_code)]
601+
/// pub struct Foo(Vec<u32>, Vec<String>);
602+
///
603+
/// impl std::borrow::Borrow<[u32]> for Foo {
604+
/// fn borrow(&self) -> &[u32] { &self.0 }
605+
/// }
606+
///
607+
/// impl std::borrow::Borrow<[String]> for Foo {
608+
/// fn borrow(&self) -> &[String] { &self.1 }
609+
/// }
610+
/// ```
583611
#[unstable(feature = "slice_concat_trait", issue = "27747")]
584-
pub trait SliceConcat<Separator: ?Sized>: Sized {
612+
pub trait Concat<Item: ?Sized> {
585613
#[unstable(feature = "slice_concat_trait", issue = "27747")]
586614
/// The resulting type after concatenation
587615
type Output;
588616

589617
/// Implementation of [`[T]::concat`](../../std/primitive.slice.html#method.concat)
590618
#[unstable(feature = "slice_concat_trait", issue = "27747")]
591-
fn concat(slice: &[Self]) -> Self::Output;
619+
fn concat(slice: &Self) -> Self::Output;
620+
}
621+
622+
/// Helper trait for [`[T]::join`](../../std/primitive.slice.html#method.join)
623+
#[unstable(feature = "slice_concat_trait", issue = "27747")]
624+
pub trait Join<Separator> {
625+
#[unstable(feature = "slice_concat_trait", issue = "27747")]
626+
/// The resulting type after concatenation
627+
type Output;
592628

593629
/// Implementation of [`[T]::join`](../../std/primitive.slice.html#method.join)
594630
#[unstable(feature = "slice_concat_trait", issue = "27747")]
595-
fn join(slice: &[Self], sep: &Separator) -> Self::Output;
631+
fn join(slice: &Self, sep: Separator) -> Self::Output;
596632
}
597633

598-
#[unstable(feature = "slice_concat_ext",
599-
reason = "trait should not have to exist",
600-
issue = "27747")]
601-
impl<T: Clone, V: Borrow<[T]>> SliceConcat<T> for V {
634+
#[unstable(feature = "slice_concat_ext", issue = "27747")]
635+
impl<T: Clone, V: Borrow<[T]>> Concat<T> for [V] {
602636
type Output = Vec<T>;
603637

604-
fn concat(slice: &[Self]) -> Vec<T> {
638+
fn concat(slice: &Self) -> Vec<T> {
605639
let size = slice.iter().map(|slice| slice.borrow().len()).sum();
606640
let mut result = Vec::with_capacity(size);
607641
for v in slice {
608642
result.extend_from_slice(v.borrow())
609643
}
610644
result
611645
}
646+
}
647+
648+
#[unstable(feature = "slice_concat_ext", issue = "27747")]
649+
impl<T: Clone, V: Borrow<[T]>> Join<&T> for [V] {
650+
type Output = Vec<T>;
612651

613-
fn join(slice: &[Self], sep: &T) -> Vec<T> {
652+
fn join(slice: &Self, sep: &T) -> Vec<T> {
614653
let mut iter = slice.iter();
615654
let first = match iter.next() {
616655
Some(first) => first,
617656
None => return vec![],
618657
};
619-
let size = slice.iter().map(|slice| slice.borrow().len()).sum::<usize>() + slice.len() - 1;
658+
let size = slice.iter().map(|v| v.borrow().len()).sum::<usize>() + slice.len() - 1;
620659
let mut result = Vec::with_capacity(size);
621660
result.extend_from_slice(first.borrow());
622661

@@ -628,6 +667,29 @@ impl<T: Clone, V: Borrow<[T]>> SliceConcat<T> for V {
628667
}
629668
}
630669

670+
#[unstable(feature = "slice_concat_ext", issue = "27747")]
671+
impl<T: Clone, V: Borrow<[T]>> Join<&[T]> for [V] {
672+
type Output = Vec<T>;
673+
674+
fn join(slice: &Self, sep: &[T]) -> Vec<T> {
675+
let mut iter = slice.iter();
676+
let first = match iter.next() {
677+
Some(first) => first,
678+
None => return vec![],
679+
};
680+
let size = slice.iter().map(|v| v.borrow().len()).sum::<usize>() +
681+
sep.len() * (slice.len() - 1);
682+
let mut result = Vec::with_capacity(size);
683+
result.extend_from_slice(first.borrow());
684+
685+
for v in iter {
686+
result.extend_from_slice(sep);
687+
result.extend_from_slice(v.borrow())
688+
}
689+
result
690+
}
691+
}
692+
631693
////////////////////////////////////////////////////////////////////////////////
632694
// Standard trait implementations for slices
633695
////////////////////////////////////////////////////////////////////////////////

src/liballoc/str.rs

+13-8
Original file line numberDiff line numberDiff line change
@@ -37,7 +37,7 @@ use core::unicode::conversions;
3737

3838
use crate::borrow::ToOwned;
3939
use crate::boxed::Box;
40-
use crate::slice::{SliceConcat, SliceIndex};
40+
use crate::slice::{Concat, Join, SliceIndex};
4141
use crate::string::String;
4242
use crate::vec::Vec;
4343

@@ -71,17 +71,22 @@ pub use core::str::SplitAsciiWhitespace;
7171
#[stable(feature = "str_escape", since = "1.34.0")]
7272
pub use core::str::{EscapeDebug, EscapeDefault, EscapeUnicode};
7373

74-
#[unstable(feature = "slice_concat_ext",
75-
reason = "trait should not have to exist",
76-
issue = "27747")]
77-
impl<S: Borrow<str>> SliceConcat<str> for S {
74+
/// Note: `str` in `Concat<str>` is not meaningful here.
75+
/// This type parameter of the trait only exists to enable another impl.
76+
#[unstable(feature = "slice_concat_ext", issue = "27747")]
77+
impl<S: Borrow<str>> Concat<str> for [S] {
7878
type Output = String;
7979

80-
fn concat(slice: &[Self]) -> String {
81-
Self::join(slice, "")
80+
fn concat(slice: &Self) -> String {
81+
Join::join(slice, "")
8282
}
83+
}
84+
85+
#[unstable(feature = "slice_concat_ext", issue = "27747")]
86+
impl<S: Borrow<str>> Join<&str> for [S] {
87+
type Output = String;
8388

84-
fn join(slice: &[Self], sep: &str) -> String {
89+
fn join(slice: &Self, sep: &str) -> String {
8590
unsafe {
8691
String::from_utf8_unchecked( join_generic_copy(slice, sep.as_bytes()) )
8792
}

0 commit comments

Comments
 (0)