Почему я могу вернуть ссылку на принадлежащее значение функции?

В главе 19.2 документа Язык программирования Rust следующий пример компилируется без ошибок. Я узнал из выпуск № 1834, что существует новое правило исключения на всю жизнь, которое неявно делает 's длиннее 'c.

Хотя мне не удалось найти подробного объяснения этого нового правила исключения, я полагаю, что это не более чем просто неявная версия более длинного и более явного ограничения: <'c, 's: 'c>. Однако я думаю, что мое замешательство, вероятно, не в этом новом правиле исключения, но, конечно, я могу ошибаться в этом.

Насколько я понимаю, parse_context становится владельцем context, поскольку он не был заимствован, а фактически перемещен в функцию. Одно это для меня означает, что время жизни context должно соответствовать времени жизни функции, которой он принадлежит, независимо от времени жизни и ограничений, которые мы определили в Context и Parser.

Основываясь на этих определениях, часть, где context переживает временное Parser, имеет для меня смысл (в конце концов, мы определили более длительный срок службы), но часть, где ссылка &str не удаляется, когда context выходит за пределы области в конце parse_context и я все еще могу его смело вернуть - меня озадачивает.

Что я пропустил? Как компилятор может определить время жизни возвращенного &str?

ОБНОВЛЕННЫЙ ПРИМЕР

struct Context<'s>(&'s str);

struct Parser<'c, 's>
{
    context: &'c Context<'s>,
}

impl<'c, 's> Parser<'c, 's>
{
    fn parse(&self) -> Result<(), &'s str>
    {
        Err(self.context.0)
    }
}

fn parse_context(context: Context) -> Result<(), &str>
{
    Parser { context: &context }.parse()
}

fn main()
{
    let mut s = String::new();
    s += "Avail";
    s += "able?";
    if let Err(text) = parse_context(Context(&s))
    {
        println!("{}", text);
    }
}

person Peter Varo    schedule 23.04.2019    source источник


Ответы (3)


struct Context<'s>(&'s str);

→ Значения типа Context содержат строку с некоторым временем жизни 's. Это время жизни неявно, по крайней мере, равно времени жизни контекста, но может быть и дольше.

struct Parser<'c, 's>
{
    context: &'c Context<'s>,
}

→ Значения типа Parser содержат ссылку на контекст с некоторым временем жизни 'c. Этот контекст содержит строку с некоторым другим временем жизни 's.

impl<'c, 's> Parser<'c, 's>
{
    fn parse(&self) -> Result<(), &'s str>
    {
        Err(self.context.0)
    }
}

→ Функция parse возвращает значение с временем жизни 's, т.е. с тем же временем жизни, что и у строки, хранящейся внутри контекста, который не совпадает со временем жизни самого контекста.

fn parse_context(context: Context) -> Result<(), &str>
{
    Parser { context: &context }.parse()
}

→ Я не уверен, где именно это указано, но, очевидно, компилятор делает вывод, что время жизни возвращаемой строки такое же, как параметр 's, используемый для контекста. Обратите внимание, что даже если сам контекст перемещен в parse_context, это влияет только на сам контекст, а не на содержащуюся в нем строку.

fn main()
{
    let mut s = String::new();

→ Создать новую строку, действительную до конца main

    s += "Avail";
    s += "able?";
    if let Err(text) = parse_context(Context(&s))

→ Создайте новый контекст и переместите его в parse_context. Он будет автоматически удален в конце parse_context. Он содержит ссылку на строку s, которая действительна до конца main, а parse_context возвращает строку с тем же временем жизни, что и stext, действительна до конца main.

    {
        println!("{}", text);
    }
}

→ Нет проблем: text действительно до конца main.

person Jmb    schedule 24.04.2019
comment
Это гораздо более подробное объяснение того же вывода, который я резюмировал в своем ответе. Это было именно то, что я искал. Спасибо! - person Peter Varo; 24.04.2019
comment
Всегда используйте rustfmt для форматирования кода в соответствии с инструкциями. Вы можете найти его под инструментами в правом верхнем углу детской площадки. - person hellow; 24.04.2019

Вы не возвращаете ссылку на собственное значение функции. Вы возвращаете копию переданной ссылки.

Ваша функция - это усовершенствованная версия функции идентификации:

fn parse_context(s: &str) -> &str {
    s
}

В вашем реальном коде вы берете ссылку на структуру, содержащую строковый фрагмент, затем другую ссылку на строковый фрагмент, но все эти ссылки отбрасываются.

Например, в parse есть ненужная ссылка:

fn parse(&self) -> Result<(), &'s str> {
    Err( self.context.0)
    //  ^ no & needed
}

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

#![deny(rust_2018_idioms)]

fn parse_context(context: Context<'_>) -> Result<(), &'_ str> {
    Parser { context: &context }.parse()
}

Смотрите также:

Хотя я не смог найти подробного объяснения этого нового правила исключения,

T: 'a вывод в структурах в руководстве по выпуску.

person Shepmaster    schedule 24.04.2019
comment
Тогда позвольте мне уточнить: в функции идентификации копия s (тип которой является ссылкой) не является копией самой ссылки, но также и целым объектом, на который она ссылается (в нашем случае - срез)? - person Peter Varo; 24.04.2019
comment
Нет, копия s действительно является копией ссылки. Однако в вашем случае это ссылка на 'static строку "Available?", определенную в вашей main функции, которая никогда не будет удалена, поскольку она 'static. - person Jmb; 24.04.2019
comment
@Jmb это именно то, что я думал ранее, но, как вы можете видеть, я обновил пример - даже если я создаю String в куче, добавляю к нему несколько символов, а затем перехожу к Context (в этот момент я не Не думаю, что базовый &str имеет 'static) код по-прежнему компилируется и работает без проблем .. Почему? - person Peter Varo; 24.04.2019
comment
Просто подумайте: действителен ли этот фрагмент, потому что временный Context, созданный в области main, будет удален только в конце области if let, потому что он становится владельцем возвращаемого значения .parse()? - person Peter Varo; 24.04.2019
comment
Это больше не 'static, но это все еще ссылка на строку, определенную в main, поэтому она все еще действительна в течение main. IOW, время жизни 's предполагается до конца функции main. - person Jmb; 24.04.2019
comment
@Jmb Я думаю, что теперь понимаю, опубликовал ответ, объясняющий мое понимание этого. Как вы думаете, сейчас это правильно? - person Peter Varo; 24.04.2019
comment
@PeterVaro, это не «мой» стиль, это стиль сообщества Rust, согласованный через RFC и принятый rustfmt. Я редактирую все вопросы и ответы по Rust, чтобы обеспечить единообразие для людей, которые ищут ответы на проблемы, не желая изучать произвольные прихоти свободного пространства плакатов. Задача SO - быстро прочитать вопрос, чтобы увидеть, актуален ли он, и получить ответ, чтобы получить информацию. Неидиоматический выбор стиля отвлекает от этой цели. - person Shepmaster; 24.04.2019
comment
@Shepmaster да, за исключением того, что ни стандартная библиотека, ни другие официальные фрагменты кода не следуют точно таким же правилам, как я узнал до сих пор, и rustfmt (слава богу) настраивается и не так глупо агрессивен, как gofmt или prettier .. - person Peter Varo; 24.04.2019

Благодаря комментариям Jmb и некоторым фрагментам ответа Шепмастера теперь мне действительно ясно, что мое замешательство было связано с правилами RAII и правом собственности.

То есть строка была создана в области main, анонимный Context экземпляр не принимает владение строкой, он только заимствует ссылку, поэтому даже когда экземпляр отбрасывается в конце parse_context вдоль с заимствованной ссылкой ссылка, скопированная на объект Err, все еще существует и указывает на существующую строку - следовательно, мы используем ограниченные переменные времени жизни, и компилятор может определить время жизни внутренней ссылки на строку.

person Peter Varo    schedule 24.04.2019