Почему я могу вернуть ссылку на собственное значение функции? - PullRequest
1 голос
/ 24 апреля 2019

В главе 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);
    }
}

Ответы [ 3 ]

2 голосов
/ 24 апреля 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 логический вывод в структурах в руководстве по изданию.

1 голос
/ 24 апреля 2019
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.

0 голосов
/ 24 апреля 2019

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

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

...