Por que o `std :: mem :: drop` não é exatamente o mesmo que o fechamento | _ | () nos limites de características com classificação mais alta?

13

A implementação de std::mem::dropestá documentada para ser a seguinte:

pub fn drop<T>(_x: T) { }

Como tal, eu esperaria que o fechamento |_| ()(conhecido coloquialmente como fechamento do vaso sanitário ) fosse um potencial substituto 1: 1 para drop, em ambas as direções. No entanto, o código abaixo mostra que dropnão é compatível com uma característica de classificação mais alta vinculada ao parâmetro da função, enquanto o fechamento do banheiro é.

fn foo<F, T>(f: F, x: T)
where
    for<'a> F: FnOnce(&'a T),
{
    dbg!(f(&x));
}

fn main() {
    foo(|_| (), "toilet closure"); // this compiles
    foo(drop, "drop"); // this does not!
}

Mensagem de erro do compilador:

error[E0631]: type mismatch in function arguments
  --> src/main.rs:10:5
   |
1  | fn foo<F, T>(f: F, x: T)
   |    ---
2  | where
3  |     for<'a> F: FnOnce(&'a T),
   |                ------------- required by this bound in `foo`
...
10 |     foo(drop, "drop"); // this does not!
   |     ^^^
   |     |
   |     expected signature of `for<'a> fn(&'a _) -> _`
   |     found signature of `fn(_) -> _`

error[E0271]: type mismatch resolving `for<'a> <fn(_) {std::mem::drop::<_>} as std::ops::FnOnce<(&'a _,)>>::Output == ()`
  --> src/main.rs:10:5
   |
1  | fn foo<F, T>(f: F, x: T)
   |    ---
2  | where
3  |     for<'a> F: FnOnce(&'a T),
   |                ------------- required by this bound in `foo`
...
10 |     foo(drop, "drop"); // this does not!
   |     ^^^ expected bound lifetime parameter 'a, found concrete lifetime

Considerando que dropé supostamente genérico em relação a qualquer tamanho T, não parece razoável que a assinatura "mais genérica" fn(_) -> _não seja compatível for<'a> fn (&'a _) -> _. Por que o compilador não está admitindo a assinatura dropaqui e o que o torna diferente quando o fechamento do vaso sanitário é colocado em seu lugar?

E_net4 the downvoter
fonte

Respostas:

4

O cerne da questão é que dropnão é uma função única, mas um conjunto de funções parametrizado que cada um elimina algum tipo específico. Para satisfazer um limite de característica de classificação mais alta (doravante hrtb), você precisaria de uma única função que possa fazer referências simultaneamente a um tipo com uma vida útil determinada.


Usaremos dropcomo nosso exemplo típico de uma função genérica, mas tudo isso se aplica de maneira mais geral também. Aqui está o código para referência: fn drop<T>(_: T) {}.

Conceitualmente, dropnão é uma função única, mas uma função para cada tipo possível T. Qualquer instância específica de dropleva apenas argumentos de um único tipo. Isso é chamado de monomorfização . Se um diferente Té usado com drop, uma versão diferente do dropé compilada. É por isso que você não pode passar uma função genérica como argumento e usar essa função em total generalidade (consulte esta pergunta )

Por outro lado, uma função como fn pass(x: &i32) -> &i32 {x}satisfaz o hrtb for<'a> Fn(&'a i32) -> &'a i32. Ao contrário drop, temos uma única função que satisfaz simultaneamente Fn(&'a i32) -> &'a i32a toda a vida 'a. Isso se reflete em como passpode ser usado.

fn pass(x: &i32) -> &i32 {
    x
}

fn two_uses<F>(f: F)
where
    for<'a> F: Fn(&'a i32) -> &'a i32, // By the way, this can simply be written
                                       // F: Fn(&i32) -> &i32 due to lifetime elision rules.
                                       // That applies to your original example too.
{
    {
        // x has some lifetime 'a
        let x = &22;
        println!("{}", f(x));
        // 'a ends around here
    }
    {
        // y has some lifetime 'b
        let y = &23;
        println!("{}", f(y));
        // 'b ends around here
    }
    // 'a and 'b are unrelated since they have no overlap
}

fn main() {
    two_uses(pass);
}

(Parque infantil)

No exemplo, as vidas 'ae 'bnão têm relação entre si: nenhuma delas abrange completamente a outra. Portanto, não há nenhum tipo de subtipo aqui. Uma única instância de passrealmente está sendo usada com duas vidas diferentes e não relacionadas.

É por isso dropque não satisfaz for<'a> FnOnce(&'a T). Qualquer instância específica de droppode cobrir apenas uma vida útil (ignorando a subtipagem). Se passamos droppara two_usesa partir do exemplo acima (com ligeiras alterações de assinatura e assumindo o compilador deixar-nos), ele teria que escolher alguns vida em particular 'ae a instância do dropno âmbito da two_usesseria Fn(&'a i32)por algum concreto vida 'a. Como a função se aplicaria apenas à vida útil única 'a, não seria possível usá-la com duas vidas não relacionadas.

Então, por que o fechamento do vaso sanitário recebe um hrtb? Ao inferir o tipo para um fechamento, se o tipo esperado sugerir que é necessário um limite de característica com classificação mais alta, o compilador tentará fazer um ajuste . Nesse caso, ele consegue.


A edição nº 41078 está intimamente relacionada a isso e, em particular, o comentário de eddyb aqui fornece essencialmente a explicação acima (embora no contexto de fechamentos, em vez de funções comuns). O problema em si não aborda o problema atual. Em vez disso, aborda o que acontece se você atribuir o fechamento do vaso sanitário a uma variável antes de usá-lo (experimente!).

É possível que a situação mude no futuro, mas exigiria uma grande mudança na forma como as funções genéricas são monomorfizadas.

SCappella
fonte
4

Em suma, ambas as linhas devem falhar. Porém, como uma etapa da maneira antiga de lidar com a vida útil do hrtb, a verificação de vazamento , atualmente tem algum problema de integridade, rustcacaba (incorretamente) aceitando uma e deixando a outra com uma mensagem de erro bastante ruim.

Se você desativar a verificação de vazamento rustc +nightly -Zno-leak-check, poderá ver uma mensagem de erro mais sensata:

error[E0308]: mismatched types
  --> src/main.rs:10:5
   |
10 |     foo(drop, "drop");
   |     ^^^ one type is more general than the other
   |
   = note: expected type `std::ops::FnOnce<(&'a &str,)>`
              found type `std::ops::FnOnce<(&&str,)>`

Minha interpretação desse erro é que o &xcorpo da foofunção possui apenas uma vida útil de escopo confinada ao referido corpo, e f(&x)também possui a mesma vida útil de escopo que não pode satisfazer a for<'a>quantificação universal exigida pela característica ligada.

A pergunta que você apresenta aqui é quase idêntica à edição # 57642 , que também possui duas partes contrastantes.

A nova maneira de processar a vida útil da hrtb é usando os chamados universos . A Niko possui um WIP para lidar com a verificação de vazamentos com universos. Sob esse novo regime, diz-se que ambas as partes do problema nº 57642 acima vinculadas falham com diagnósticos muito mais claros. Suponho que o compilador também possa manipular seu código de exemplo corretamente até então.

edwardw
fonte