Какова связь между временем жизни заимствованной ссылки на вектор и заимствованными указателями, которые он содержит?

Примечание редактора. Этот пример кода относится к версии Rust до 1.0 и не является синтаксически верным кодом Rust 1.0. Обновленные версии этого кода вызывают различные ошибки, но ответы по-прежнему содержат ценную информацию.

Я попробовал этот код в Rust 0.6:

fn test<'r>(xs: &'r [&str]) -> &'r str {
    return xs[0];
}

Я думаю, что сигнатура типа означает: "test берет заимствованный указатель с временем жизни r на вектор заимствованных указателей на строки и возвращает заимствованный указатель на строку, также с временем жизни r. Но компилятор говорит:

refs.rs:2:8: 2:12 error: mismatched types: expected `&'r str` but found `&str` (lifetime mismatch)
refs.rs:2       return xs[0];
                       ^~~~
refs.rs:1:39: 3:1 note: the lifetime &'r  as defined on the block at 1:39...
refs.rs:1 fn test<'r>(xs: &'r [&str]) -> &'r str {
refs.rs:2       return xs[0];
refs.rs:3 }
refs.rs:1:39: 3:1 note: ...does not necessarily outlive the anonymous lifetime #1 defined on the block at 1:39
refs.rs:1 fn test<'r>(xs: &'r [&str]) -> &'r str {
refs.rs:2       return xs[0];
refs.rs:3 }
error: aborting due to previous error

Кажется, это подразумевает, что указатели внутри вектора могут жить не так долго, как сам вектор (только для чтения). Это возможно?

Есть ли какая-то дополнительная аннотация, которую я должен сделать, чтобы сказать компилятору, что это нормально?

Точно так же, как насчет векторов собственных указателей? например

fn test<'r>(xs: &'r [~str]) -> &'r str {
    return xs[0];
}

Опять же, я ожидаю, что смогу позаимствовать указатель на элемент вектора хотя бы до тех пор, пока я позаимствовал весь список.

Для контекста моей первоначальной проблемой была попытка дополнить список заимствованных точек списком собственных указателей:

fn extend<'r>(xs: ~[&'r str], ys: &'r [~str]) -> ~[&'r str]

План заключался в следующем: создать расширенный список со всеми заимствованными указателями, использовать его, затем освободить расширенный список, а затем освободить исходный список принадлежащих указателей, включая содержащиеся в нем строки.

4 ответа

Решение

Вторая версия test с собственными / уникальными строками работает, компилятору нужно помочь преобразовать ~str к &'r str:

fn test<'r>(xs: &'r [~str]) -> &'r str {
    let tmp: &'r str = xs[0];
    tmp
}

Причиной этого является xs вектор владеет ~str s он содержит, и поэтому компилятор знает, что их время жизни - это, по крайней мере, время жизни вектора (потому что он также осторожен с изменчивостью при наличии заимствований, подобных этой, поэтому строку никогда нельзя удалить из вектора). Единственная проблема - убедить компилятор привести xs[0] к срезу, который легче всего сделать временным.


extend может выглядеть примерно так:

fn extend<'r>(xs: ~[&'r str], ys: &'r [~str]) -> ~[&'r str] {
    let mut xs = xs;
    for vec::each(ys) |s| {
        let tmp: &'r str = *s;
        xs.push(tmp)
    }
    xs
}

Кажется, что vec::each(ys) работает, но ys.each нет, что, вероятно, является ошибкой (сейчас я изучаю ее и открыл # 6655).

Если вы хотите изменить вектор на месте, нормальным методом является передача изменяемой ссылки на вектор, т.е.

fn extend<'r>(xs: &mut ~[&'r str], ys: &'r [~str]) {
    for vec::each(ys) |s| {
        let tmp: &'r str = *s;
        xs.push(tmp)
    }
}

который называется как extend(&mut vec, additions),


Проиллюстрировать:

rusti> let a = &[~"a", ~"b", ~"c"];
()
rusti> test(a)
"a"
rusti> extend(~["1", "2", "3"], a)
~["1", "2", "3", "a", "b", "c"]

Я думаю, что вы имеете в виду:

fn test<'r>(xs: &[&'r str]) ->  &'r str {
        return xs[0];
}

То есть вы берете указатель заимствованного массива, содержащий заимствованные указатели на строки с временем жизни r, и возвращаете один из этих указателей, то же время жизни. Время жизни самого вектора не имеет значения.

Вот почему ваш второй пример не сработает, потому что вы возвращаете заимствованный указатель, который не был заимствован во входных данных функции: массив заимствован, а его содержимое - нет.

Я думаю, что это то, что вы имеете в виду:

fn get1<'r, T>(xs: &'r [T]) -> &'r T {
    return &xs[0];
}

fn main() {
    let a = ~[1, 2, 3];
    let b = [1, 2, 3];
    let c = @[1, 2, 3];
    let ax = get1(a);
    let bx = get1(b);
    let cx = get1(c);
    println(fmt!("%d %d %d", *ax, *bx, *cx));
}

В частности, для строк это может быть не слишком хорошо (поскольку строки всегда являются ссылками), но с вектором значений - это работает просто отлично.

Начиная с Rust 1.19.0 (и, возможно, начиная с Rust 1.0), оригинальный код работает, как и ожидалось:

fn test<'r>(xs: &'r [&str]) -> &'r str {
    xs[0]
}

fn main() {}

Собственная строковая версия также работает после обновления синтаксиса:

fn test<'r>(xs: &'r [String]) -> &'r str {
    &xs[0]
}

fn main() {}

Более того, логический вывод означает, что вам не нужно явно указывать время жизни для функций (fn test(xs: &[&str]) -> &str, fn test(xs: &[String]) -> &str)

У меня такое ощущение, что эта проблема сводится к тому, как компилятор вычисляет (со-, противо-, не-) дисперсию времени жизни, или, точнее, как он не рассчитал это правильно для этого случая до Rust 1.0. Как вы правильно определили, поскольку срез содержит ссылки, ссылки должны пережить срез. Из-за этого безопасно возвращать фрагмент строки с более коротким соответствием времени жизни 'r,

Другие вопросы по тегам