Noções básicas sobre tempos de vida: vida máxima e 'estático

Sep 16 2020

Meu processo de aprendizagem para a vida útil da ferrugem era assim (baseado no livro sobre ferrugem):

  1. Eu quero anotar, quando os valores atrás das referências saem do escopo
  2. Normalmente (nem sempre! Consulte a seção .data, ou seja, 'estático), os valores residem em um {}bloco
  3. Nós anotamos blocos como 't: {…}e, por exemplo, campos de estrutura obtêm um tempo de vida &'t identcom o mesmo nome de tempo de vidat
  4. Esse entendimento está errado. Por quê? As definições de nome de bloco são provavelmente desconhecidas para o implementador de estrutura e pode haver várias definições de nome de bloco para a mesma estrutura.
  5. Portanto, a definição 't: {…}e o uso &'t identdevem ser completamente independentes.
  6. Os compiladores podem determinar facilmente as definições, portanto os usuários nunca precisam escrever 't: {…}. Os programadores só precisam se preocupar com a &'t identparte da especificação.
  7. Os compiladores podem analisar corpos de função (no caso de struct: uso dos membros da estrutura) e determinar a &'t identparte.
  8. Esse entendimento está errado. Por quê? Porque às vezes o corpo da função (ou uso de membros de estrutura) ainda não está disponível (por exemplo, um traço especifica uma função, mas a implementação é feita por outra parte no futuro).
  9. Como resultado, structe fndeve especificar totalmente os tempos de vida em sua definição de estrutura ou assinatura de função, respectivamente.
  10. As especificações geralmente seguem as mesmas regras heurísticas. Portanto, introduzimos a elisão vitalícia. Ele insere vidas úteis com base em regras que visam os casos de uso mais comuns e podemos cancelar a qualquer momento.

Neste ponto, acho que meu entendimento está bem próximo de como isso realmente funciona. Mas agora, meu entendimento está errado. Vejamos alguns exemplos:

#[derive(Debug)]
struct Stats {
  league: &str,
}

const NAME: &str = "rust";

fn more_difficult_league(s1: &Stats, s2: &Stats) -> &str {
  if s1.league == s2.league {
    s1.league
  } else if s1.league == "PHP" {
    s2.league
  } else {
    "C++"
  }
}


fn main() {
  let mut st = Stats { league: name };
  let dleague = more_difficult_league(&st, &st);
  println!("{}", dleague);
}

Obviamente, omiti todas as especificações vitalícias.

  • O tempo de vida dos campos de estrutura é toda a duração do programa ( 'static) ou enquanto a estrutura ( Stats<'a>com league: &'a str)

  • Em uma função / método, podemos obter referências com vidas 'a, 'b, 'c, .... Qual é a vida útil do valor de retorno?

    • Ou é algum valor estático ( 'static)
    • Ou é sempre a mesma vida útil específica (como 'c)
    • Ou é um tempo de vida específico - qual será conhecido em tempo de compilação ou execução. Para o compilador, devemos especificar o pior caso de vida max('a, 'b, 'c, …). Até onde sei, isso pode ser feito dando a cada referência a mesma duração.

Isso parece funcionar para a seguinte função artificial e mais curta:

fn more_difficult_league<'a>(s1: &'a Stats, s2: &'a Stats) -> &'a str {
  if s1.league == s2.league {
    s1.league
  } else {
    s2.league
  }
}

Se adicionarmos algum 'staticvalor de retorno, o pior caso de vida max('a, 'static)é, presumivelmente 'static:

fn more_difficult_league<'a>(s1: &'a Stats, s2: &'a Stats) -> &'static str {
  if s1.league == s2.league {
    s1.league
  } else if s1.league == "PHP" {
    s2.league
  } else {
    "C++"
  }
}

Isso dá error[E0621]: explicit lifetime required in the type of s1e lifetime 'static requiredpara s2.league.

Em que ponto meu entendimento está errado? Agradeço antecipadamente por ter paciência comigo.

Aviso: help: add explicit lifetime 'static to the type of s1: &'a Stats<'static> funcionaria aqui, mas parece errado para mim.

Respostas

2 prog-fh Sep 16 2020 at 09:01

Eu mudaria seu código conforme fornecido abaixo.

Em vez de fingir que o resultado de more_difficult_league()tem um tempo de vida estático (o que não é o caso quando nos referimos a s1ou s2, e o compilador reclama sobre isso), podemos introduzir uma nova anotação de tempo de vida para esse resultado e especificar que os tempos de vida dos parâmetros devem sobreviver a esse resultado (a wherecláusula).

#[derive(Debug)]
struct Stats<'a> {
    league: &'a str,
}

const NAME: &str = "rust";

fn more_difficult_league<'a, 'b, 'c>(
    s1: &'a Stats,
    s2: &'b Stats,
) -> &'c str
where
    'a: 'c,
    'b: 'c,
{
    if s1.league == s2.league {
        s1.league
    } else if s1.league == "PHP" {
        s2.league
    } else {
        "C++"
    }
}

fn main() {
    let st = Stats { league: NAME };
    let dleague = more_difficult_league(&st, &st);
    println!("{}", dleague);
}