[Solved] How to split a Cow?


#1

Hi rusties !

I have an enum embedding a Cow<str> which I’d like to split in two, by modifying self itself. However I don’t quite get how to do that.
Here’s what I tried:

use std::borrow::Cow;

enum BoxKind<'a> {
    Text(Cow<'a, str>),
}

struct DomBox<'a> {
    kind: BoxKind<'a>,
}

impl<'a> DomBox<'a> {
    fn split(&mut self, pos: usize) -> Option<DomBox<'a>> {
        match self.kind {
            BoxKind::Text(ref mut text) => {
                let (cut, remains) = text.split_at(pos);
                *text = Cow::from(cut);
                Some(DomBox { kind: BoxKind::Text(Cow::from(remains)) })
            }
        }
    }
}

Of course this fails because of error[E0495]: cannot infer an appropriate lifetime for pattern due to conflicting requirements. I tried to mem::replace() the Cow but it didn’t work either. I can’t seems to explain to rustc how the lifetimes propagate through split_at(). Could someone enlighten me ?


#2

I think one issue is that the lifetime of ref mut text is constrained by both the lifetime 'a and the anonymous lifetime of &mut self, making it shorter than 'a.

The other, more important issue, is that you don’t handle the Cow::Owned version. The lifetimes could work out if they were all annotated in for Cow::Borrowed, but if the text is owned then you’re creating a pair of references into the text, then dropping that text (by replacing it in *text = ...) while the references are still alive.

You could implement split_at on Cow<'a, str> directly, then return either a pair of Cow::Borrowed or a pair of Cow::Owned depending on what the input is.


#3

Ah you’re right, I have made 2 cases for each Cow variant, but I still have a problem:

use std::borrow::Cow;

enum BoxKind<'a> {
    Text(Cow<'a, str>),
}

struct DomBox<'a> {
    kind: BoxKind<'a>,
}

impl<'a> DomBox<'a> {
    fn split(&mut self, pos: usize) -> Option<DomBox<'a>> {
        match self.kind {
            BoxKind::Text(Cow::Borrowed(ref mut text)) => {
                let (cut, remains) = text.split_at(pos);
                *text = cut;
                Some(DomBox { kind: BoxKind::Text(Cow::from(remains)) })
            }
            BoxKind::Text(Cow::Owned(ref mut text)) => {
                let (cut, remains) = text.split_at(pos);
                *text = cut.to_owned();
                Some(DomBox { kind: BoxKind::Text(Cow::from(remains.to_owned())) })
            }
        }
    }
}
error[E0506]: cannot assign to `*text` because it is borrowed
  --> splitline.rs:21:17
   |
20 |                 let (cut, remains) = text.split_at(pos);
   |                                      ---- borrow of `*text` occurs here
21 |                 *text = cut.to_owned();
   |                 ^^^^^^^^^^^^^^^^^^^^^^ assignment to borrowed `*text` occurs here

I need to do it atomically apparently. I’m not sure how.


#4

Maybe try it like this:

let mut remains = None;
(*text, remains) = {
    let (cut, _remains) = text.split_at(pos);
    (cut, Some(_remains))
};

#5

I think you need a string version of Vec::split_off so you can mutate the existing owned string, but that doesn’t seem to exist :frowning:

Maybe replace

let (cut, remains) = text.split_at(pos);
*text = cut.to_owned();

with something like

let remains = text[pos..].to_owned();
text.truncate(pos);

#6

Taking @Nemo157’s idea: https://is.gd/O0jxTS


#7

Wow thanks guys !
Let me try all of this :slight_smile: