Rust: problema de rastreamento const fn (RFC 911)

Criado em 6 abr. 2015  ·  274Comentários  ·  Fonte: rust-lang/rust

https://github.com/rust-lang/rust/issues/57563 | novo problema de rastreamento de meta

Conteúdo antigo

Problema de rastreamento para rust-lang/rfcs#911.

Este problema foi encerrado em favor de problemas mais direcionados:

Coisas a serem feitas antes de estabilizar:

CTFE = https://en.wikipedia.org/wiki/Compile_time_function_execution

A-const-eval A-const-fn B-RFC-approved B-RFC-implemented B-unstable C-tracking-issue T-lang

Comentários muito úteis

Por favor, ignore isso se eu estiver completamente fora do ponto.

O problema que vejo com este RFC é que, como usuário, você deve marcar o maior número possível de funções const fn porque essa provavelmente será a melhor prática. A mesma coisa está acontecendo atualmente em C++ com contexpr. Eu acho que isso é apenas verbosidade desnecessária.

D não tem const fn mas permite que qualquer função seja chamada em tempo de compilação (com algumas exceções).

por exemplo

// Standalone example.
struct Point { x: i32, y: i32 }

impl Point {
    fn new(x: i32, y: i32) -> Point {
        Point { x: x, y: y }
    }

    fn add(self, other: Point) -> Point {
        Point::new(self.x + other.x, self.y + other.y)
    }
}

const ORIGIN: Point = Point::new(0, 0); // works because 0, 0 are both known at compile time 
const ORIGIN2: Point = Point::new(0, 0); // ditto

const ANOTHER: Point = ORIGIN.add(ORIGIN2); // works because ORIGIN and ORIGIN2 are both const.
{
    let x: i32 = 42;
    let y: i32 = 24;
    const SOME_POINT: Point = Point::new(x, y); // Error: x and y are not known at compile time
}
{
    const x: i32 = 42;
    const y: i32 = 24;
    const SOME_POINT: Point = Point::new(x, y); // Works x and y are both known at compile time.
}

Observe que eu não sou realmente um usuário do Rust e só li o RFC há alguns minutos, então é possível que eu tenha entendido mal alguma coisa.

Todos 274 comentários

Isso está fechado por # 25609?

@Munksgaard Isso apenas adiciona suporte ao compilador AFAIK. Há muitas funções no stdlib que precisam ser alteradas para const fn e testadas quanto a quebras. Não sei qual é o progresso nisso.

Espero que isso seja implementado em std::ptr::null() e null_mut() para que possamos usá-los para inicializar static mut *MyTypeWithDrop sem recorrer a 0usize as *mut _

EDIT: Removido porque estava fora do assunto

Para ser claro, a questão aqui não é principalmente sobre a utilidade do recurso, mas sim sobre a melhor maneira de formulá-lo (ou a melhor estrutura para formulá-lo). Veja a discussão RFC.

Este é agora o problema de rastreamento para uma eventual estabilização.

https://github.com/rust-lang/rust/issues/29107 foi fechado.

Eu discordo que "Integração com padrões" ou qualquer alteração na biblioteca padrão deva bloquear isso. Isso é muito útil mesmo sem essas alterações, e essas alterações podem ser feitas posteriormente. Em particular, gostaria de começar a usar const fn em meu próprio código em breve.

Assim, o status de estabilização disso poderia ser reavaliado?

Não duvido que const fn mesmo em sua forma limitada atual, seja uma funcionalidade útil, mas o que eu realmente gostaria, de preferência antes de seguir esse caminho, seria para aqueles a favor do " const fn abordagem" para pensar e articular seu final de jogo preferido. Se continuarmos adicionando funcionalidades aparentemente úteis da maneira mais óbvia, parece-me muito provável que acabemos copiando mais ou menos a totalidade do design constexpr do C++. Isso é algo com que nos sentimos confortáveis? Mesmo se dissermos sim, preferiria que escolhêssemos esse caminho com clareza, em vez de recuarmos com pequenos passos ao longo do tempo, como o caminho de menor resistência, até que se torne inevitável.

(Dado que a semântica do código Rust seguro deve ser totalmente definível, parece provável que, eventualmente, pelo menos todas as funções que não dependam (transitivamente) de unsafe possam ser marcadas como const . E dado que unsafe deve ser um detalhe de implementação, eu aposto que as pessoas vão pressionar para de alguma forma afrouxar essa restrição também. Eu preferiria que olhássemos para o exterior e tentássemos encontrar uma solução mais coesa, capaz e história bem integrada para simulação e computação em nível de tipo.)

@glaebhoerl

Não duvido que const fn, mesmo em sua forma limitada atual, seja uma funcionalidade útil, mas o que eu realmente gostaria, idealmente antes de seguir esse caminho, seria para aqueles a favor da "abordagem const fn" para pense e articule seu final de jogo preferido... me parece muito provável que eventualmente acabemos copiando mais ou menos a totalidade do design constexpr do C++.

O que eu pessoalmente gostaria, ainda mais do que isso, é que tivéssemos uma visão bastante clara de como vamos implementá-lo e que parte da linguagem vamos cobrir. Dito isto, isso está intimamente relacionado ao suporte para constantes associadas ou genéricos sobre inteiros em minha mente.

@eddyb e eu fizemos alguns esboços recentemente em um esquema que poderia permitir a avaliação constante de uma faixa muito ampla de código. Basicamente, abaixando todas as constantes para MIR e interpretando-o (em alguns casos,
interpretação abstrata, se houver genéricos que você ainda não pode avaliar, que é onde as coisas ficam mais interessantes para mim).

No entanto, embora pareça ser bastante fácil suportar uma fração muito grande da "linguagem interna", o código real na prática atinge a necessidade de fazer alocação de memória muito rapidamente. Em outras palavras, você quer usar Vec ou algum outro container. E é aí que todo esse esquema de interpretação começa a ficar mais complicado na minha cabeça.

Dito isso, @glaebhoerl , também adoraria ouvir você articular seu final de jogo alternativo preferido. Acho que você esboçou alguns desses pensamentos no const fn RFC, mas acho que seria bom ouvi-lo novamente, e neste contexto. :)

O problema com a alocação é fazer com que ela escape em tempo de execução.
Se pudermos de alguma forma não permitir cruzar essa barreira de tempo de compilação/tempo de execução, acredito que poderíamos ter um liballoc funcionando com const fn .
Não seria mais difícil gerenciar esses tipos de alocações do que lidar com valores endereçáveis ​​por byte em uma pilha interpretada.

Alternativamente, poderíamos gerar código de tempo de execução para alocar e preencher os valores toda vez que essa barreira tiver que ser ultrapassada, embora eu não tenha certeza de que tipo de casos de uso ela possui.

Tenha em mente que mesmo com uma avaliação completa do tipo constexpr , const fn _ainda_ seria puro: executá-lo duas vezes em dados 'static resultaria exatamente no mesmo resultado e não efeitos colaterais.

@nikomatsakis Se eu tivesse um, teria mencionado. :) Eu principalmente só vejo desconhecidos conhecidos. A coisa toda com const s como parte do sistema genérico era, claro, parte do que eu entendi como sendo o design C++. Quanto a ter associado os parâmetros genéricos const s e const , considerando que já temos arrays de tamanho fixo com const s como parte de seu tipo e gostaríamos de abstrair sobre deles, eu ficaria surpreso se houvesse uma maneira muito melhor - em oposição a apenas mais geral - de fazer isso. A parte const fn das coisas parece mais separável e variável. É fácil imaginar levar as coisas adiante e ter coisas como const impl s e const Trait limites em genéricos, mas tenho certeza que há arte anterior para esse tipo de coisa geral que já descobriu as coisas e devemos tentar encontrá-lo.

Dos principais casos de uso da linguagem Rust, aqueles que precisam principalmente de controle de baixo nível, como kernels, já parecem razoavelmente bem servidos, mas outra área em que Rust pode ter muito potencial são coisas que precisam principalmente de alto desempenho e, em esse suporte poderoso de espaço (de alguma forma) para computação encenada (da qual const fn já é uma instância muito limitada) parece que pode ser um divisor de águas. (Apenas nas últimas semanas me deparei com dois tweets separados de pessoas que decidiram mudar do Rust para um idioma com melhores recursos de preparação.) Não tenho certeza se alguma das soluções existentes em idiomas "perto de nós" -- constexpr C++, CTFE ad-hoc de D, nossas macros procedurais -- realmente parecem inspiradoras e poderosas/completas o suficiente para esse tipo de coisa. (Macros procedurais parecem uma coisa boa de se ter, mas mais para abstração e DSLs, não tanto para geração de código orientada ao desempenho.)

Quanto ao que _seria_ inspirador e bom o suficiente... ainda não vi, e não estou familiarizado o suficiente com todo o espaço para saber, precisamente, onde procurar. É claro que, de acordo com o exposto, podemos querer pelo menos dar uma olhada em Julia e Terra, mesmo que pareçam linguagens bastante diferentes de Rust de várias maneiras. Eu sei que Oleg Kiselyov fez um trabalho muito interessante nesta área. O trabalho de Tiark Rompf em Lancet e Lightweight Modular Staging para Scala parece definitivamente digno de ser visto. Lembro-me de ver uma apresentação de @kmcallister em algum momento sobre como seria um Rust de tipo dependente (que pode pelo menos ser mais geral do que colocar const em todos os lugares), e também me lembro de ver algo de Oleg para o efeito que os próprios tipos são uma forma de encenação (o que parece natural, considerando que a separação de fases entre compilação e tempo de execução é muito parecida com estágios)... oportunidade se nos comprometermos com a primeira solução que nos ocorrer. :)

(Isso foi apenas um braindump e quase certamente caracterizei imperfeitamente muitas coisas.)

No entanto, embora pareça ser bastante fácil suportar uma fração muito grande da "linguagem interna", o código real na prática atinge a necessidade de fazer alocação de memória muito rapidamente. Em outras palavras, você deseja usar o Vec ou algum outro contêiner. E é aí que todo esse esquema de interpretação começa a ficar mais complicado na minha cabeça.

Discordo dessa caracterização de "código real na prática". Acho que há um grande interesse no Rust porque ajuda a reduzir a necessidade de alocação de memória heap. Meu código, em particular, faz um esforço concreto para evitar alocação de heap sempre que possível.

Ser capaz de fazer mais do que isso seria _legal_, mas ser capaz de construir instâncias estáticas de tipos não triviais com invariantes impostas pelo compilador é essencial. A abordagem C++ constexpr é extremamente limitante, mas é mais do que preciso para meus casos de uso: preciso fornecer uma função que possa construir uma instância do tipo T com parâmetros x , y e z tal que a função garanta que x , y e z são válidos (por exemplo, x < y && z > 0 ), de forma que o resultado possa ser uma variável static , sem o uso do código de inicialização que é executado na inicialização.

@briansmith FWIW outra abordagem que tem a chance de resolver os mesmos casos de uso seria se as macros tivessem higiene de privacidade , o que acredito (espero) que estamos planejando fazê-las.

@briansmith FWIW outra abordagem que tem a chance de resolver os mesmos casos de uso seria se as macros tivessem higiene de privacidade, o que acredito (espero) que estamos planejando fazê-las.

Acho que se você usar as macros procedurais, poderá avaliar x < y && z > 0 em tempo de compilação. Mas parece que levaria muitos, muitos meses até que macros procedurais pudessem ser usadas no Rust estável, se é que são. const fn é interessante porque pode ser habilitado para Rust _now_ estável, até onde eu entendo o estado das coisas.

@glaebhoerl Eu não prenderia a respiração por higiene rigorosa, é bem possível que tenhamos um mecanismo de escape (assim como os LISPs reais), então você pode não querer isso para qualquer tipo de segurança.

@glaebhoerl também existe https://anydsl.github.io/, que até usa
Sintaxe semelhante a Rust ;) eles estão basicamente visando computação em estágios e em
determinada avaliação parcial.

No sábado, 16 de janeiro de 2016 às 18h29, Eduard-Mihai Burtescu <
[email protected]> escreveu:

@glaebhoerl https://github.com/glaebhoerl Eu não prenderia a respiração por
higiene rigorosa, é bem possível que tenhamos um mecanismo de escape
(assim como LISPs reais), então você pode não querer isso para qualquer tipo de segurança
propósitos.


Responda a este e-mail diretamente ou visualize-o no GitHub
https://github.com/rust-lang/rust/issues/24111#issuecomment -172271960.

29525 deve ser corrigido antes da estabilização

Dado que a semântica do código Rust seguro deve ser totalmente definível, parece provável que, eventualmente, pelo menos todas as funções que não dependam (transitivamente) de unsafe possam ser marcadas como const . E dado que unsafe deveria ser um detalhe de implementação, eu aposto que as pessoas vão pressionar para de alguma forma afrouxar essa restrição também.

Apenas um pensamento: se alguma vez definimos formalmente o modelo de memória de Rust , até mesmo o código unsafe poderia ser avaliado de forma segura e sensata em tempo de compilação, interpretando-o abstratamente/simbolicamente - ou seja, o uso de ponteiros brutos não t se transformam em acessos diretos à memória como em tempo de execução, mas sim algo (apenas como exemplo para ilustração) como uma pesquisa em um mapa de hash de endereços alocados, juntamente com seus tipos e valores, ou semelhantes, com cada etapa verificada quanto à validade - então que qualquer execução cujo comportamento seja indefinido seria _estritamente_ um erro relatado pelo compilador, em vez de uma vulnerabilidade de segurança em rustc . (Isso também pode estar conectado à situação em que manipula isize e usize em tempo de compilação simbolicamente ou de maneira dependente da plataforma.)

Não tenho certeza de onde isso nos deixa em relação a const fn . Por um lado, isso provavelmente abriria código muito mais útil para estar disponível em tempo de compilação -- Box , Vec , Rc , qualquer coisa usando unsafe para otimização de desempenho - o que é bom. Uma restrição arbitrária a menos. Por outro lado, o limite para "o que pode ser um const fn " agora seria essencialmente movido para fora para _qualquer coisa que não envolva o FFI_. Então, o que estaríamos _realmente_ rastreando no sistema de tipos, sob o disfarce de const ness, é o que as coisas (transitivamente) dependem do FFI e o que não. E se algo usa ou não o FFI é _ainda_ algo que as pessoas legitimamente consideram ser um detalhe de implementação interna, e essa restrição (diferente unsafe ) _realmente_ não parece viável de ser levantada. E neste cenário você provavelmente teria muito mais fn s sendo elegível para const ness do que aqueles que não seriam.

Portanto, você ainda teria const girando em torno de uma restrição arbitrária de exposição de implementação, e também acabaria tendo que escrever const em quase todos os lugares. Isso também não soa muito atraente...

ou seja, o uso de ponteiros brutos não se transformaria em acessos diretos à memória como em tempo de execução, mas sim em algo ... como uma pesquisa em um mapa de hash de endereços alocados,

@glaebhoerl Bem, esse é praticamente o modelo que descrevi e que o miri de @tsion está implementando.

Acho que a distinção FFI é muito importante por causa da pureza, que é _necessária_ para a coerência.
Você _não poderia_ usar GHC para Rust const fn s porque tem unsafePerformIO .

Eu não gosto muito da palavra-chave const e é por isso que estou bem com const fn foo<T: Trait> em vez de const fn foo<T: const Trait> (por exigir um const impl Trait for T ).

Assim como Sized , provavelmente temos os padrões errados, mas não vi nenhuma outra proposta que possa funcionar de forma realista.

@eddyb Acho que você quis vincular a https://internals.rust-lang.org/t/mir-constant-evaluation/3143/31 (comentário 31, não 11).

@tsion Corrigido, obrigado!

Por favor, ignore isso se eu estiver completamente fora do ponto.

O problema que vejo com este RFC é que, como usuário, você deve marcar o maior número possível de funções const fn porque essa provavelmente será a melhor prática. A mesma coisa está acontecendo atualmente em C++ com contexpr. Eu acho que isso é apenas verbosidade desnecessária.

D não tem const fn mas permite que qualquer função seja chamada em tempo de compilação (com algumas exceções).

por exemplo

// Standalone example.
struct Point { x: i32, y: i32 }

impl Point {
    fn new(x: i32, y: i32) -> Point {
        Point { x: x, y: y }
    }

    fn add(self, other: Point) -> Point {
        Point::new(self.x + other.x, self.y + other.y)
    }
}

const ORIGIN: Point = Point::new(0, 0); // works because 0, 0 are both known at compile time 
const ORIGIN2: Point = Point::new(0, 0); // ditto

const ANOTHER: Point = ORIGIN.add(ORIGIN2); // works because ORIGIN and ORIGIN2 are both const.
{
    let x: i32 = 42;
    let y: i32 = 24;
    const SOME_POINT: Point = Point::new(x, y); // Error: x and y are not known at compile time
}
{
    const x: i32 = 42;
    const y: i32 = 24;
    const SOME_POINT: Point = Point::new(x, y); // Works x and y are both known at compile time.
}

Observe que eu não sou realmente um usuário do Rust e só li o RFC há alguns minutos, então é possível que eu tenha entendido mal alguma coisa.

@MaikKlein houve muita discussão sobre CTFE na discussão RFC

Não vejo comentários recentes explicando os bloqueadores aqui, e a operação não é muito esclarecedora. Qual é o estado. Como podemos mover isso através da linha de chegada?

Consulte https://github.com/rust-lang/rust/issues/29646#issuecomment -271759986. Também precisamos reconsiderar nossa posição sobre a clareza, já que miri empurra o limite para "efeitos colaterais globais" ( @solson e @nikomatsakis estavam falando sobre isso no IRC).

O problema que vejo com este RFC é que, como usuário, você deve marcar o maior número possível de funções const fn, porque essa provavelmente será a melhor prática.

Embora pudéssemos tornar chamadas funções arbitrárias, se essas funções acessarem código C ou estática, não poderemos calculá-las. Como solução, sugiro um lint que avisará sobre funções públicas que podem ser const fn.

Concordo sobre o fiapo. É semelhante aos lints internos existentes missing_docs , missing_debug_implementations e missing_copy_implementations .

No entanto, há um problema em ter o lint ativado por padrão ... ele avisaria sobre funções que você explicitamente não deseja que sejam const , digamos, porque você planeja alterar posteriormente a função de forma que ela não pode ser const e não quer comprometer sua interface com const (remover const é uma mudança radical).

Acho que #[allow(missing_const)] fn foo() {} pode funcionar nesses casos?

@eddyb @nikomatsakis Meu ponto de "remoção de const é uma mudança radical" sugere que vamos querer ter a palavra-chave afinal, já que é uma promessa de downstream que o fn _remain_ const até a próxima versão principal.

Será uma pena quanto const precisará ser espalhado por std e outras bibliotecas, mas não vejo como você pode evitar isso, a menos que seja exigido apenas em público- itens de frente, e isso parece uma regra confusa.

a menos que fosse exigido apenas em itens voltados para o público, e isso parece uma regra confusa.

Eu gosto deste... Eu não acho que seria confuso. Sua interface pública está protegida, pois você não pode fazer uma função não-const que é chamada por um const fn

Tecnicamente, seria melhor anotar funções como notconst , porque espero que haja muito mais const fn do que o contrário.

notconst também seria mais consistente com a filosofia de design da Rust. (ou seja, " mut , não const ")

a menos que fosse exigido apenas em itens voltados para o público, e isso parece uma regra confusa.

Eu gosto deste... Eu não acho que seria confuso.

Estou pirando nessa ideia. Ele tem seus benefícios (pense apenas em const fn ao tomar decisões de interface pública), mas pensei em outra maneira de ser confuso:

Sua interface pública está protegida, pois você não pode fazer uma função não-const que é chamada por um const fn

Isso é verdade e, infelizmente, isso implicaria que quando um autor de biblioteca marca uma função pública const , ele está marcando implicitamente todas as funções chamadas transitivamente por essa função const também, e há uma chance eles estão marcando involuntariamente funções que não querem, impedindo-os de reescrever essas funções internas usando recursos não const no futuro.


Espero que haja muito mais const fn do que o contrário.

Eu pensei assim por um tempo, mas só será verdade para caixas de biblioteca Rust puras. Não será possível fazer fns const baseados em FFI (mesmo que sejam apenas baseados em FFI transitivamente, o que é muita coisa), então a grande quantidade de const fn pode não ser tão ruim como você e eu pensamos.


Minha conclusão atual: Qualquer const fn não explícito parece problemático. Pode não haver uma boa maneira de evitar escrever muito a palavra-chave.

Além disso, para o registro, notconst seria uma mudança de ruptura.

@solson Um ponto muito bom.

Tenha em mente que a palavra-chave fica ainda mais complicada se você tentar usá-la com métodos de traço. Restringi-lo à definição de traço não é útil o suficiente e anotar impls resulta em regras imperfeitas "const fn paramerim".

Eu sinto que essa troca foi bastante discutida quando adotamos const fn em primeiro lugar. Acho que a análise do @solson também está correta. Acho que a única coisa que mudou é que talvez a porcentagem de policiais fns tenha crescido, mas não acho que o suficiente para mudar a troca fundamental aqui. Vai ser chato ter que adicionar gradualmente const fn em suas interfaces públicas e assim por diante, mas a vida é assim.

@nikomatsakis O que está me incomodando é a combinação desses dois fatos:

  • não podemos verificar tudo com antecedência, o código unsafe pode ser "dinamicamente não constante"
  • o que quer que façamos para genéricos e implements de traços, será uma troca entre "correto" e flexível

Dado que "efeitos colaterais globais" é a principal coisa que impede que o código seja const fn , esse não é o "sistema de efeito" que o Rust costumava ter e foi removido?
Não deveríamos falar sobre "estabilidade de efeito"? Parece semelhante ao código assumindo que alguma biblioteca nunca entra em pânico IMO.

@eddyb absolutamente const é um sistema de efeitos e sim, ele vem com todas as desvantagens que nos fizeram querer evitá-los o máximo possível ... em um sistema de efeitos, podemos querer considerar alguma sintaxe que podemos escalar para outros tipos de efeitos. Como exemplo, estamos pagando um preço semelhante com unsafe (também um efeito), embora eu não tenha certeza de que faça sentido pensar em unificá-los.

O fato de que as violações podem ocorrer dinamicamente parece ainda mais uma razão para fazer esse opt-in, não?

Que tal agora:

Em geral, acho que const fn s deve ser usado apenas para construtores ( new ) ou onde for absolutamente necessário.

No entanto, às vezes você pode querer usar outros métodos para criar convenientemente uma constante. Acho que poderíamos resolver esse problema para muitos casos tornando constness o padrão , mas apenas para o módulo de definição . Dessa forma, os dependentes não podem assumir constness a menos que seja explicitamente garantido com const , enquanto ainda têm a conveniência de criar constantes com funções sem tornar tudo const .

@torkleyy Você já pode fazer isso com ajudantes que não são exportados.

Não vejo um argumento forte de que as funções auxiliares privadas não devem ser implicitamente const , quando possível. Acho que @solson estava dizendo que tornar const explícito, mesmo para funções auxiliares, força o programador a pausar e considerar se eles querem se comprometer com essa função sendo const . Mas se os programadores já são obrigados a pensar nisso para funções públicas, isso não é suficiente? Não valeria a pena não ter que escrever const em todos os lugares?

No IRC @eddyb propôs dividir esse portão de recurso para que pudéssemos estabilizar chamadas para const fns antes de descobrir detalhes de sua declaração e corpos. Isso te parece uma boa ideia?

@durka Isso parece ótimo para mim, como um usuário do Rust que não sabe muito sobre os internos do compilador.

Desculpe minha falta de compreensão aqui, mas o que significa estabilizar a chamada para uma função const sem estabilizar a declaração.

Estamos dizendo que o compilador de alguma forma saberá o que é e o que não é constante por alguns meios, mas deixará essa parte aberta para discussão/implementação por enquanto?

Como então as chamadas podem ser estabilizadas se o compilador pode mudar de ideia mais tarde sobre o que é constante?

@nixpulvis Alguns const fn s já existem na biblioteca padrão, por exemplo UnsafeCell::new . Esta proposta permitiria chamar tais funções em contextos constantes, por exemplo o inicializador de um item static .

@nixpulvis O que eu quis dizer foram chamadas para funções const fn definidas pelo código de uso instável (como a biblioteca padrão), a partir de contextos constantes, não funções regulares definidas no código Rust estável.

Embora eu seja a favor de estabilizar as chamadas para const fn s primeiro, se isso puder acontecer mais rápido, não está claro para mim o que está bloqueando a estabilização de todo o recurso const fn . Quais são as preocupações restantes hoje? Qual seria um caminho para abordá-los?

@SimonSapin É mais que não temos certeza de que o design para declarar const fn s hoje escala bem, nem temos certeza sobre as interações entre eles e as características e quanta flexibilidade deve haver.

Acho que estou inclinado a estabilizar os usos de const fn. Isso parece uma vitória ergonômica e de expressividade e ainda não consigo imaginar uma maneira melhor de lidar com a avaliação constante em tempo de compilação do que apenas ser capaz de "escrever código normal".

estabilizar usos de const fn.

Isso também estabiliza algumas funções na biblioteca padrão como sendo const , a equipe da biblioteca deve fazer pelo menos alguma auditoria.

Enviei um PR https://github.com/rust-lang/rust/issues/43017 para estabilizar invocações, juntamente com uma lista de funções a serem auditadas por @petrochenkov.

Eu tenho uma pergunta/comentário sobre como isso pode ser usado em certas situações de trait/impl. Hipoteticamente, digamos que temos uma biblioteca de matemática com um traço Zero :

pub trait Zero {
    fn zero () -> Self;
}

Essa característica não exige que o método zero seja const , pois isso impediria que ele fosse implementado por algum tipo BigInt apoiado por um Vec . Mas para escalares de máquina e outros tipos simples, seria muito mais prático se o método fosse const .

impl Zero for i32 {
    const fn zero () -> i32 { 0 } // const
}

impl Zero for BigInt {
    fn zero () -> BigInt { ... } // not const
}

A característica não exige que o método seja const , mas ainda deve ser permitido, pois const está adicionando uma restrição à implementação e não ignorando uma. Isso evita ter uma versão normal e uma versão const da mesma função para alguns tipos. O que eu estou querendo saber é que isso já foi abordado?

Por que você deseja que diferentes implementações da característica se comportem de maneira diferente? Você não pode usar isso em um contexto genérico. Você pode simplesmente fazer um impl local no escalar com um const fn.

@Daggerbot Essa é a única maneira que vejo para const fn em traços - ter o traço exigir que todos os impls sejam const fn é muito menos comum do que ter efetivamente " const impl s" .

@jethrogb Você poderia, embora exija que a constness seja uma propriedade do impl.
O que tenho em mente é que um const fn genérico com um, por exemplo, T: Zero vinculado, exigirá o impl de Zero para o T s é chamado para conter apenas métodos const fn , quando a chamada vem de um contexto constante em si (por exemplo, outro const fn ).

Não é perfeito, mas nenhuma alternativa superior foi apresentada - IMO, o mais próximo disso seria "permitir quaisquer chamadas e erros profundos da pilha de chamadas se algo impossível em tempo de compilação for tentado", o que não é tão ruim quanto pode parecer à primeira impressão - a maior parte da preocupação sobre isso tem a ver com compatibilidade com versões anteriores, ou seja, marcar uma função const fn garante que o fato seja registrado e a execução de operações inválidas em tempo de compilação exigiria torná-la não const fn .

Isso não resolveria o problema?

pub trait Zero {
    fn zero() -> Self;
}

pub trait ConstZero: Zero {
    const fn zero() -> Self;
}

impl<T: ConstZero> Zero for T {
    fn zero() -> Self {
        <Self as ConstZero>::zero()
    }
}

O clichê pode ser reduzido com macros.

Além do pequeno inconveniente de ter duas características separadas ( Zero e ConstZero ) que fazem quase exatamente a mesma coisa, vejo um problema potencial ao usar uma implementação geral:

// Blanket impl
impl<T: ConstZero> Zero for T {
    fn zero () -> Self { T::const_zero() }
}

pub struct Vector2<T> {
    pub x: T,
    pub y: T,
}

impl<T: ConstZero> ConstZero for Vector2<T> {
    const fn const_zero () -> Vector2<T> {
        Vector2 { x: T::const_zero(), y: T::const_zero() }
    }
}

// Error: This now conflicts with the blanket impl above because Vector2<T> implements ConstZero and therefore Zero.
impl<T: Zero> Zero for Vector2<T> {
    fn zero () -> Vector2<T> {
        Vector2 { x: T::zero(), y: T::zero() }
    }
}

O erro desapareceria se removêssemos o impl do cobertor. Em suma, isso é provavelmente o mais fácil de implementar em um compilador, pois adiciona menos complexidade à linguagem.

Mas se pudéssemos adicionar const a um método implementado onde não é necessário, podemos evitar essa duplicação, embora ainda não perfeitamente:

impl<T: Zero> Zero for Vector2<T> {
    const fn zero () -> Vector2<T> {
        Vector2 { x: T::zero(), y: T::zero() }
    }
}

IIRC, C++ permite algo assim ao trabalhar com constexpr . A desvantagem aqui é que este const só seria aplicável se <T as Zero>::zero também fosse const . Isso deve ser um erro ou o compilador deve ignorar este const quando não for aplicável (como C++)?

Nenhum desses exemplos aborda o problema perfeitamente, mas não consigo pensar em uma maneira melhor.

Edit: a sugestão de @andersk tornaria o primeiro exemplo possível sem erros. Esta seria provavelmente a solução melhor/mais simples no que diz respeito à implementação do compilador.

@Daggerbot Isso soa como um caso de uso para a regra “lattice” proposta perto do final da RFC 1210 (especialização) . Se você escrever

impl<T: ConstZero> Zero for T {…}  // 1
impl<T: ConstZero> ConstZero for Vector2<T> {…}  // 2
impl<T: Zero> Zero for Vector2<T> {…}  // 3
impl<T: ConstZero> Zero for Vector2<T> {…}  // 4

então, embora 1 se sobreponha a 3, sua interseção é coberta precisamente por 4, de modo que seria permitido sob a regra da rede.

Veja também http://smallcultfollowing.com/babysteps/blog/2016/09/24/intersection-impls/.

Esse é um sistema incrivelmente complexo, que queremos evitar.

Sim, a regra de rede seria necessária.

@eddyb o que você considera complexo?

@Kixunil Duplicando quase todos os traços na biblioteca padrão, em vez de "simplesmente" marcar alguns impl s como const fn .

Estamos saindo da pista aqui. Atualmente a questão é sobre estabilizar os usos de const fn . Permitindo métodos de traço const fn ou const impl Trait for Foo são ortogonais entre si e com os RFCs aceitos.

@oli-obk Este não é o novo RFC, mas o problema de rastreamento para const fn .

Acabei de perceber e editei meu comentário.

@eddyb sim, mas é mais simples para o compilador (menos especialização, mas provavelmente vamos querer especialização de qualquer maneira) e permite que as pessoas também sejam vinculadas por ConstTrait .

De qualquer forma, não me oponho a marcar impls como const. Também estou imaginando o compilador gerando automaticamente ConstTrait: Trait .

@Kixunil Não é muito mais simples, especialmente se você puder fazer isso com especialização.
O compilador não teria que gerar automaticamente nada como ConstTrait: Trait , nem o sistema de traits precisa saber sobre nada disso, basta recorrer às implementações (ou um impl concreto ou um limite where ) que o sistema de características fornece e verifique-os.

Eu estou querendo saber se const fns deve proibir acessos a UnsafeCell . Provavelmente é necessário permitir um comportamento verdadeiramente const:

const fn dont_change_anything(&self) -> bool {
    let old = self.cell.get();
    self.cell.set(!old);
    old
}

Até agora eu vi que set não é const . A questão é se isso vai ficar para sempre. Em outras palavras: o código unsafe pode confiar que, ao executar a mesma função const em dados imutáveis, sempre retornará o mesmo resultado hoje e em todas as versões futuras da linguagem/biblioteca?

const fn não significa imutável, significa que pode ser chamado em tempo de compilação.

Entendo. Eu apreciaria muito se eu pudesse de alguma forma garantir que uma função sempre retorna a mesma coisa quando chamada várias vezes sem usar traços unsafe , se for possível de alguma forma.

@jethrogb Obrigado pelo link!

Percebi que mem::size_of é implementado como const fn todas as noites. Isso seria possível para mem::transmute e outros? Os intrínsecos do Rust operam internamente ao compilador e não estou familiarizado o suficiente para fazer as alterações apropriadas para permitir isso. Caso contrário, eu ficaria feliz em implementá-lo.

Infelizmente, operar com valores é um pouco mais difícil do que criar alguns magicamente. transmute requer miri. Um primeiro passo para colocar o miri no compilador já está em andamento: #43628

Assim! Algum interesse em estabilizar *Cell::new , mem::{size,align}_of , ptr::null{,_mut} , Atomic*::new , Once::new e {integer}::{min,max}_value ? Devemos ter FCPs aqui ou criar problemas de rastreamento individuais?

sim.

Não faço parte de nenhuma equipe que tenha poder de decisão sobre isso, mas minha opinião pessoal é que todos esses, exceto mem::{size,align}_of são triviais o suficiente para que possam ser estabilizados agora sem passar pelos movimentos de um carimbo de borracha FCP.

Como usuário, gostaria de usar mem::{size,align}_of em expressões const o mais rápido possível, mas li @nikomatsakis expressando preocupações sobre eles serem insta-const-stable quando foram feitos const fn s . Não sei se há preocupações específicas ou apenas cautela geral, mas o IIRC é por isso que os portões de recursos por função foram adicionados. Imagino que as preocupações desses dois sejam semelhantes o suficiente para que possam compartilhar um FCP. Não sei se @rustbot pode lidar com FCPs separados no mesmo thread do GitHub, então provavelmente é melhor abrir problemas separados.

@durka você pode abrir um único problema de rastreamento para estabilizar a constness de todas essas funções? Vou propor o FCP assim que terminar.

Para seguir um exemplo em uma discussão sobre const fns em alloc::Layout :
O pânico pode ser permitido em um const fn e tratado como um erro de compilação? Isso é semelhante ao que é feito agora com expressões aritméticas constantes, não é?

Sim, esse é um recurso super trivial quando o miri é mesclado

Este é o lugar certo para solicitar funções std adicionais se tornando const ? Nesse caso, Duration:: { new , from_secs , from_millis } devem ser seguros para fazer const .

@remexre A maneira mais fácil de fazer isso acontecer é provavelmente fazer um PR e pedir uma revisão da equipe de libs lá.

PR'd como https://github.com/rust-lang/rust/pull/47300. Eu também adicionei const aos construtores instáveis ​​enquanto estava nisso.

Alguma ideia sobre permitir que outras funções std sejam declaradas const ? Especificamente, mem::uninitialized e mem::zeroed ? Eu acredito que ambos são candidatos adequados para funções adicionais de const . A única desvantagem que posso pensar é a mesma desvantagem de mem::uninitialized , onde a criação de estruturas que implementam Drop são criadas e escritas sem um ptr::write .

Eu posso anexar um PR também se isso parecer adequado.

Qual é a motivação para isso? Parece uma arma inútil para permitir a criação de padrões de bits inválidos que não podem ser substituídos (porque estão em const), mas talvez eu esteja ignorando o óbvio.

mem::uninitialized é absolutamente uma espingarda, que atira através de suas mãos também se apontada incorretamente. Sério, não posso exagerar o quão incrivelmente perigoso o uso dessa função pode ser, apesar de sua marcação como unsafe .

A motivação por trás da declaração dessas funções adicionais const decorre da natureza dessas funções, pois chamar mem::uninitialized<Vec<u32>> retornará o mesmo resultado todas as vezes, sem efeitos colaterais. Obviamente, se não inicializado, isso é uma coisa terrível de se fazer. Portanto, o unsafe ainda está presente.

Mas para um caso de uso, considere um cronômetro global, que rastreie o início de alguma função. Seu estado interno será determinado posteriormente, mas precisamos de uma maneira de apresentá-lo como uma estrutura global estática criada na execução.

use std::time::Instant;

pub struct GlobalTimer {
    time: UnsafeCell<Instant>
}

impl TimeManager {
    pub const fn init() -> TimeManager {
        TimeManager {
            time: UnsafeCell::new(Instant::now())
        }
    }
}

Este código não compila, pois Instant::now() não é uma função const . Substituir Instant::now() por mem::uninitialized::<Instant>()) resolveria esse problema se mem::uninitialized fosse um const fn . Idealmente, o desenvolvedor inicializará essa estrutura assim que o programa iniciar a execução. E embora esse código seja considerado ferrugem não idiomática (o estado global geralmente é muito ruim), esse é apenas um dos muitos casos em que as estruturas estáticas globais são úteis.

Acho que este post fornece uma boa base para o futuro do código Rust sendo executado em tempo de compilação. Estruturas estáticas globais em tempo de compilação são um recurso com alguns casos de uso importantes (os sistemas operacionais também vêm à mente) que a ferrugem está faltando no momento. Pequenos passos podem ser feitos em direção a esse objetivo, pensando lentamente em adicionar const às funções da biblioteca consideradas adequadas, como mem::uninitialized e mem::zeroed , apesar de suas marcações unsafe .

Edit: Esqueci o const na assinatura da função de TimeManager::init()

Hmm, esse código compila, então ainda estou perdendo a motivação exata aqui ... se você pudesse escrever código como

const fn foo() -> Whatever {
    unsafe { 
        let mut it = mem::uninitialized();
        init_whatever(&mut it);
        it
    }
}

Mas const fns estão atualmente tão restritos que você nem pode escrever isso ...

Eu aprecio a justificativa teórica, mas const não é o mesmo que pure e não acho que devemos fazer nada para incentivar o uso dessas funções se não for necessário para algum caso de uso convincente .

Eu acho que há frutas penduradas muito mais baixas que podem ser estabilizadas primeiro. Sem miri, os intrínsecos não inicializados e zerados fazem pouco sentido de qualquer maneira. Eu gostaria de vê-los algum dia embora. Poderíamos até estabilizá-los inicialmente e exigir que todas as constantes produzissem um resultado inicializado, mesmo que os cálculos intermediários pudessem ser não inicializados.

Dito isto, com uniões e código inseguro, você pode emular não inicializado ou zerado de qualquer maneira, então não há muito sentido em mantê-los não const

Com a ajuda de union s, o código anterior agora compila . É absolutamente aterrorizante 😅.

Todos os pontos positivos também. Essas funções intrínsecas são bem baixas na lista de casos de uso, mas ainda são candidatas adequadas para eventual const -ness.

Isso é assustadoramente incrível.

Então... por que exatamente você está defendendo a constifying mem::uninitialized, as
oposto a, digamos, Instant::agora? :)

A necessidade de ter inicializadores constantes para estruturas com
interiores é real (ver: Mutex). Mas eu não acho que fazer este malarkey
mais fácil é o caminho certo para conseguir isso!

Na quinta-feira, 25 de janeiro de 2018 às 2h21, Stephen Fleischman <
[email protected]> escreveu:

Com a ajuda de sindicatos, o código anterior agora compila
https://play.rust-lang.org/?gist=be075cf12f63dee3b2e2b65a12a3c854&version=nightly .
É absolutamente aterrorizante 😅.


Você está recebendo isso porque foi mencionado.
Responda a este e-mail diretamente, visualize-o no GitHub
https://github.com/rust-lang/rust/issues/24111#issuecomment-360382201 ,
ou silenciar o thread
https://github.com/notifications/unsubscribe-auth/AAC3n-HyWD6MUEbfHkUUXonh9ORGPSRoks5tOCtegaJpZM4D66IA
.

Instant::now não pode ser const. O que essa função retornaria? A hora da compilação?

Alguém pode resumir o que precisa ser feito para estabilizar isso? Que decisão precisa ser tomada? Se para estabilizar isso em tudo?

Integração com padrões (por exemplo, https://gist.github.com/d0ff1de8b6fc15ef1bb6)

Eu já comentei sobre a essência, mas const fn atualmente não pode ser correspondido em um padrão, isso não deve bloquear a estabilização, certo? Podemos sempre permitir depois, se fizer sentido.

Instant::agora não pode ser const. O que essa função retornaria? A hora da compilação?

Mas pode haver Instant::zero() ou Instant::min_value() que é const.

Alguém pode resumir o que precisa ser feito para estabilizar isso? Que decisão precisa ser tomada? Se para estabilizar isso em tudo?

Acho que a única questão em aberto é se nossas verificações de const fn são rigorosas o suficiente para não permitir/estabilizar acidentalmente algo que não queremos dentro de const fn.

Podemos fazer integração com padrões através de rust-lang/rfcs#2272 ? Padrões já são dolorosos como são atualmente, não vamos torná-los mais dolorosos.

Acho que a única questão em aberto é se nossas verificações de const fn são rigorosas o suficiente para não permitir/estabilizar acidentalmente algo que não queremos dentro de const fn.

Corrija-me se estiver errado, mas essas verificações não são idênticas às verificações permitidas atualmente no corpo de um const ? Fiquei com a impressão de que const FOO: Type = { body }; e const FOO: Type = foo(); const fn foo() -> Type { body } são idênticos no que permitem a qualquer corpo arbitrário

@sgrif Acho que a preocupação é em torno de argumentos, que const fn têm, mas const não.
Além disso, não está claro que a longo prazo queremos manter o sistema const fn de hoje.

Você está sugerindo genéricos const (em ambos os sentidos)? (por exemplo <const T> + const C<T> também conhecido const C<const T> ?)

Eu realmente gostaria de ter uma macro try_const! que tentará avaliar qualquer expressão em tempo de compilação e entrar em pânico se não for possível. Esta macro será capaz de chamar non-const fns (usando miri?), então não temos que esperar até que cada função em std tenha sido marcada como const fn. No entanto, como o nome indica, ela pode falhar a qualquer momento, portanto, se uma função for atualizada e agora não puder ser const, ela interromperá a compilação.

@Badel2 Entendo por que você deseja esse recurso, mas suspeito que o uso generalizado dele seria muito ruim para o ecossistema de caixas. Porque dessa forma sua grade pode acabar dependendo de uma função em outra grade sendo avaliável em tempo de compilação, e então o autor da grade altera algo que não afeta a assinatura da função, mas impede que a função seja avaliável em tempo de compilação.

Se a função foi marcada const fn em primeiro lugar, então o autor da caixa teria detectado o problema diretamente ao tentar compilar a caixa e você pode confiar na anotação.

Se isso funcionasse no playground... https://play.rust-lang.org/?gist=6c0a46ee8299e36202f959908e8189e6&version=stable

Esta é uma maneira não portátil (na verdade, tão não portátil que funciona no meu sistema, mas não no playground - mas ambos são linux) de incluir o tempo de compilação no programa compilado.

A maneira portátil seria permitir SystemTime::now() na avaliação const.

(Este é um argumento para const/compile-time-eval de QUALQUER função/expressão, independentemente de ser const fn ou não.)

Isso me parece um argumento para proibir caminhos absolutos em include_bytes 😃

Se você permitir SystemTime::now em const fn, const FOO: [u8; SystemTime::now()] = [42; SystemTime::now()]; erro aleatoriamente dependendo do desempenho do seu sistema, agendador e posição de Júpiter.

Pior ainda:

const TIME: SystemTime = SystemTime::now();

Não significa que o valor de TIME seja o mesmo em todos os sites de uso, especialmente em compilações com caixas incrementais e entre grades.

E ainda mais louco é que você pode estragar foo.clone() de maneiras muito doentias, porque você pode acabar selecionando o clone impl de um array com comprimento 3, mas o tipo de retorno pode ser um array de comprimento 4.

Portanto, mesmo se permitíssemos que funções arbitrárias fossem chamadas, nunca permitiríamos que SystemTime::new() retornasse com sucesso, assim como nunca permitiríamos geradores de números aleatórios verdadeiros

@SoniEx2 Acho que isso é um pouco offtopic aqui, mas você pode implementar algo assim já hoje usando um arquivo de carga build.rs . Consulte Build Scripts no Cargo Book, especificamente a seção sobre o estudo de caso de geração de código.

@oli-obk Acho que não é completamente o mesmo problema porque um é sobre a segurança da API de versão, enquanto o outro é sobre o ambiente de compilação, no entanto, concordo que ambos podem levar à quebra do ecossistema se não forem aplicados com cuidado.

Por favor, não permita obter a hora atual em const fn ; não precisamos adicionar formas mais/mais fáceis de tornar as compilações não reproduzíveis.

Não podemos permitir nenhum tipo const fn não-determinismo (como números aleatórios, a hora atual, etc.) a mesma entrada. Veja aqui um pouco mais de explicação.

Um método futuro para lidar com casos como em https://github.com/rust-lang/rust/issues/24111#issuecomment -376352844 seria usar uma macro procedural simples que obtém a hora atual e a emite como um número simples ou token de seqüência de caracteres. Macros procedurais são códigos mais ou menos completamente irrestritos que podem obter o tempo por qualquer uma das maneiras portáteis usuais que o código Rust não const usaria.

@rfcbot fcp mesclar

Proponho mesclar isso, porque é uma opção um tanto sensata, não é uma alteração de quebra, evita alterações de quebra acidentais (alterando uma função de uma maneira que a torne não const avaliável enquanto outras caixas usam a função em contextos const) e a única A coisa realmente ruim sobre isso é que temos que lançar const antes de um monte de declarações de função.

@rfcbot fcp merge em nome de @oli-obk - parece valer a pena pensar em estabilização e discutir os problemas

O membro da equipe @nrc propôs mesclar isso. O próximo passo é a revisão pelo resto das equipes marcadas:

  • [x] @aturon
  • [x] @cramertj
  • [ ] @eddyb
  • [x] @joshtriplett
  • [ ] @nikomatsakis
  • [x] @nrc
  • [ ] @pnkfelix
  • [x] @scottmcm
  • [x] @withoutboats

Preocupações:

  • design (https://github.com/rust-lang/rust/issues/24111#issuecomment-376829588)
  • parallel-const-traits resolvido por https://github.com/rust-lang/rust/issues/24111#issuecomment -377133537
  • prioridade (https://github.com/rust-lang/rust/issues/24111#issuecomment-376652507)
  • endereços de ponteiro de tempo de execução (https://github.com/rust-lang/rust/issues/24111#issuecomment-386745312)

Uma vez que a maioria dos revisores aprove (e nenhum se oponha), isso entrará em seu período final de comentários. Se você identificar um problema importante que não foi levantado em nenhum momento deste processo, por favor, fale!

Consulte este documento para obter informações sobre quais comandos os membros da equipe marcados podem me fornecer.

@rfcbot preocupam a prioridade

Podemos querer apostar nisso até depois da edição, já que não acho que temos largura de banda para lidar com qualquer precipitação.

@rfcbot diz respeito a tudo-const

Acabamos em um mundo C++ onde há incentivo para fazer todas as funções que você puder const .

Um resumo da breve discussão com @oli-obk:

  1. No futuro, quase todas as funções poderão ser marcadas const . Por exemplo, tudo em Vec pode ser const . Nesse mundo, pode fazer sentido se livrar completamente da palavra-chave const : quase tudo pode ser const , e você terá que se esforçar para alterar uma função de const para non -const, então os riscos de compatibilidade com versões anteriores sobre constness inferida provavelmente não seriam muito altos.

  2. No entanto, livrar-se de const hoje não é viável. O miri de hoje não pode interpretar tudo , e não é realmente testado em produção.

  3. Na verdade, é compatível com versões anteriores exigir const hoje e, em seguida, descontinuar essa palavra-chave e mudar para constness inferida no futuro.

Colocando 1, 2 e 3 juntos, parece uma boa opção estabilizar a palavra-chave const hoje, do que expandir o conjunto de funções avaliáveis ​​​​constantes em versões futuras. Depois de algum tempo, teremos um avaliador constante de texugo -de-mel testado em batalha, que pode avaliar tudo. Nesse ponto, podemos mudar para const inferido.

Escreva os perigos de precipitação: const fn tem sido muito usado em todas as noites, especialmente em embutidos. Além disso, o verificador const fn é o mesmo verificador usado para inicializadores estáticos e constantes (exceto para algumas verificações específicas estáticas e argumentos de função).

A maior desvantagem que vejo é que estamos essencialmente defendendo a pulverização de const liberalmente em caixas (por enquanto, veja o post de @matklad para ideias futuras

@rfcbot preocupam-se com características paralelas de const

Parece que estabilizar isso resultará imediatamente em um monte de caixas fazendo uma hierarquia de características paralelas com Const na frente: ConstDefault , ConstFrom , ConstInto , ConstClone , ConstTryFrom , ConstTryInto , etc e pedindo ConstIndex e tal. Isso não é terrível - certamente temos um pouco disso com Try hoje, embora estabilizar o TryFrom ajude - mas acho que seria bom pelo menos ter um esboço do plano para resolvê-lo melhor. (Isso é https://github.com/rust-lang/rfcs/pull/2237? Eu não sei).

( @nrc : Parece que o bot registrou apenas uma de suas preocupações)

Parallel-const-traits tem a solução trivial na versão futura hipotética const-all-the-things. Eles simplesmente funcionariam.

No mundo const fn você não terminaria com a duplicação de traits, contanto que não permitamos métodos const fn traits (que nós não atm), só porque você não pode. É claro que você poderia criar constantes associadas (no nightly), que é meio que a situação em que o libstd estava um ano atrás, onde tínhamos um monte de constantes para inicializar vários tipos dentro de statics/constantes, sem expor seus campos privados. Mas isso é algo que já poderia estar acontecendo há algum tempo e não aconteceu.

Para ser claro, ConstDefault já é possível hoje sem const fn , e o resto desses exemplos ( ConstFrom , ConstInto , ConstClone , ConstTryFrom , ConstTryInto ) não será possível mesmo com esse recurso estabilizado, pois não adiciona métodos de traço const como @oli-obk mencionado.

( ConstDefault é possível usando um const associado em vez de um const fn associado, mas é equivalente em poder até onde eu sei.)

@scottmcm const fn nas definições de traços não é possível hoje (oh @solson já mencionou isso).

@eddyb ideia aleatória: e se fosse possível const impl uma característica em vez de adicionar const fn nas definições de características? (Esses dois também não são mutuamente exclusivos.)

@whitequark https://github.com/rust-lang/rfcs/pull/2237 cobre essa ideia, através de uma combinação de const impl expandindo para const fn em cada fn em o impl , e permitindo que um impl com todos os métodos const satisfaça um limite T: const Trait , sem marcar nenhum dos métodos const em a própria definição do traço.

@rfcbot design de preocupação

Historicamente, apostamos na estabilização de qualquer sistema const fn específico por vários motivos:

  • o atual não suporta trait s que requerem métodos const fn , ou trait impl s que fornecem métodos const fn (veja https://github. com/rust-lang/rfcs/pull/2237 para algumas maneiras de fazer isso)
  • relacionado, existe o problema de pedir um método usado por meio de um T: Trait que deve ser const fn sem ter características separadas, e de preferência apenas quando usado em tempo de compilação (por exemplo Option::map funcionaria da mesma forma em tempo de execução, mas exigiria um encerramento const-callable em CTFE)
  • com muitos algoritmos, coleções e abstrações sendo potencialmente avaliáveis ​​por const, pode haver caixas inteiras que usariam const fn em todos os lugares ( libcore vem à mente)

Existem diferentes opções de design que aliviariam a maioria ou todos esses problemas (ao custo de introduzir outros), por exemplo, estes são alguns dos que surgiram:

  • não exigindo nenhuma anotação e apenas emitindo erros do compilador e o miri não avalia

    • prós: bases de código mais limpas, avaliação constante já pode falhar dependendo dos valores envolvidos

    • contras: sem documentação de comportamento em nível de linguagem e sem limite de semver, você pode lançar o código de qualquer outra pessoa no miri e observar qualquer pequena alteração que eles fizeram, por exemplo, log de depuração em tempo de execução sendo adicionado em uma versão de patch de uma de suas dependências; também, a promoção de rvalue é mais difícil de fazer

  • uma maneira de optar pelo comportamento acima por function/impl/module/etc.

    • os corpos dessas funções (pelo menos em termos de const fn ) se comportariam como macros

    • prós: não ter as implicações semver, limitando o escopo de algumas análises

    • contras: ainda não há grande documentação, não está claro qual comportamento deve ser afetado ( apenas const-evaluatability?), qualquer mudança no corpo pode contar como quebra de semver

Porque dessa forma sua grade pode acabar dependendo de uma função em outra grade sendo avaliável em tempo de compilação, e então o autor da grade altera algo que não afeta a assinatura da função, mas impede que a função seja avaliável em tempo de compilação.

@leoschwarz isso já não é um problema com traços automáticos? Talvez a solução para isso seja integrar o servidor de ferrugem com carga para detectar esse tipo de quebra não intencional.

Dito isso, não está claro para mim o que acontece se miri tiver um limite de tempo de avaliação que você (como autor de biblioteca) exceda acidentalmente, causando falha de compilação no downstream.

@nrc Acho que "tudo-const" é verdade, mas não é um problema. Sim, vamos acabar marcando uma grande quantidade de coisas const .

Só quero salientar que não tenho certeza se quero que tudo seja inferido
const. É uma decisão sobre se o tempo de execução ou de compilação é mais
importante. Às vezes eu acho que o compilador faz computação suficiente em
tempo de compilação!

Na quarta-feira, 28 de março de 2018 às 14h49, Josh Triplett [email protected]
escrevi:

@nrc https://github.com/nrc Acho que "everything-const" é verdade, mas não
um problema. Sim, vamos acabar marcando uma enorme faixa de coisas const.


Você está recebendo isso porque foi mencionado.
Responda a este e-mail diretamente, visualize-o no GitHub
https://github.com/rust-lang/rust/issues/24111#issuecomment-376914220 ,
ou silenciar o thread
https://github.com/notifications/unsubscribe-auth/AAC3ny9Wm9JK6p-fXf6gbaEgjFtBpMctks5ti6LigaJpZM4D66IA
.

limite de tempo de avaliação

esse limite acaba em breve.

Só quero salientar que não tenho certeza se quero que tudo inferido seja const.

Oh não, não vamos calcular coisas aleatoriamente em tempo de compilação. Apenas permita que coisas aleatórias sejam calculadas no corpo de estáticas, constantes, discriminantes de variantes de enumeração e comprimentos de matriz

@rfcbot resolvido paralelo-const-traits

Obrigado pelas correções, pessoal!

esse limite acaba em breve.

Impressionante. Nesse caso, auto-const-fn (em combinação com alguma integração de rust-semverver ou similar para fornecer informações sobre quebra) parece incrível, embora o "adicionar registro e causar quebra" possa ser problemático. Embora você possa aumentar o número da versão, eu acho, não é como se eles fossem finitos.

O registro e a impressão são efeitos colaterais "bons" no meu modelo de constantes. Poderíamos encontrar uma solução para isso se todos concordassem. Poderíamos até escrever em arquivos (não realmente, mas agir como se fizéssemos e jogar tudo fora).

Estou realmente preocupado em jogar fora silenciosamente os efeitos colaterais.

Podemos discutir isso assim que criarmos uma RFC em torno deles. Por enquanto você simplesmente não pode ter "efeitos colaterais" em constantes. O tópico é ortogonal para estabilizar const fn

Estou um pouco preocupado com a abordagem "apenas faça um semver warning" para inferir
constância. Se um autor de caixotes que nunca pensou em constância vê
"aviso: a alteração que você acabou de fazer torna impossível chamar foo() em
const context, que antes era possível", eles vão apenas ver isso como um
non-sequitur e silenciá-lo? Claramente, as pessoas nesta edição frequentemente pensam
sobre quais funções podem ser const. E seria bom se mais pessoas fizessem
que (uma vez que const_fn é estável). Mas os avisos inesperados são os corretos
maneira de incentivar isso?

Na quinta-feira, 29 de março de 2018 às 4h36, Oliver Schneider [email protected]
escrevi:

Podemos discutir isso assim que criarmos uma RFC em torno deles. Por enquanto você só
não pode ter "efeitos colaterais" em constantes. O tópico é ortogonal
estabilizando const fn


Você está recebendo isso porque foi mencionado.
Responda a este e-mail diretamente, visualize-o no GitHub
https://github.com/rust-lang/rust/issues/24111#issuecomment-377164275 ,
ou silenciar o thread
https://github.com/notifications/unsubscribe-auth/AAC3n3MtmvrDF42Iy0nhZ2q8xC-QGcvXks5tjJ0ggaJpZM4D66IA
.

Eu acho que o const fn explícito pode ser irritante e atrapalhar muitas APIs, mas acho que a alternativa de assumir implicitamente tem muitos problemas para ser praticável:

  • Jogar fora os efeitos colaterais pode fazer com que o código se comporte de maneira diferente (digamos, escrever e depois ler um arquivo) se for chamado de const ou não const.
  • Chamar funções externas significa que sempre pode haver efeitos colaterais, portanto, código inseguro provavelmente nunca poderia ser inferido const fn.
  • Quando const fn pode ser inferido para código genérico? Isso seria feito em tempo de monomorfização?

No entanto, eu realmente vejo o maior problema em não torná-lo explícito que alguém pode acidentalmente quebrar muito código com uma única alteração sem nem mesmo estar ciente disso. Isso é especialmente preocupante com os longos gráficos de dependência comuns no ecossistema Rust. Se for necessária uma alteração explícita na assinatura da função, será mais fácil perceber que essa é uma alteração de ruptura.

Talvez tal recurso possa ser implementado como um sinalizador de configuração de nível de caixa que pode ser adicionado na raiz da caixa, #![infer_const_fn] ou algo assim, e permanecer ativo para sempre. Se o sinalizador for adicionado, const fn seria inferido sempre que possível no engradado e também refletido nos documentos (e exigiria que as funções chamadas também fossem const fn), se um autor do engradado adicionasse esse sinalizador, eles se comprometeram a ser cautelosos sobre versionamento e talvez rust-semverver possa até ser forçado.

Que tal fazer ao contrário?

Em vez de ter const fn, tenha o lado fn.

Ainda é explícito (você precisa colocar o lado fn para chamar o lado fn, quebrando explicitamente a compatibilidade) e remove a desordem. (Alguns) intrínsecos e qualquer coisa com asm seria um lado fn.

Isso não é compatível com versões anteriores, embora eu ache que possa ser adicionado em uma edição?

Em 30 de março de 2018 02:43:06 GMT+08:00, "Soni L." [email protected] escreveu:

Que tal fazer ao contrário?

Em vez de ter const fn, tenha o lado fn.

Ainda é explícito (você precisa colocar o lado fn para chamar o lado fn,
quebrando explicitamente a compatibilidade) e remove a desordem. (Algum)
intrínsecos e qualquer coisa com asm seria um lado fn.

--
Você está recebendo isso porque foi mencionado.
Responda a este e-mail diretamente ou visualize-o no GitHub:
https://github.com/rust-lang/rust/issues/24111#issuecomment -377333542

--
Enviado do meu dispositivo Android com K-9 Mail. Por favor desculpe minha brevidade.

Acho que o maior problema é que seria um verdadeiro choque para iniciantes, já que não é isso que a maioria das linguagens de programação fazem.

@whitequark Eu discordo de qualquer coisa que apenas faça isso ("jogar fora os efeitos colaterais"), acho que @oli-obk estava falando sobre uma extensão futura, mas pelas discussões em que participei, sei o seguinte

  • podemos distinguir "efeitos colaterais determinísticos" (que não devolvem dados impuros)
  • poderíamos ter APIs especiais se quiséssemos, por exemplo, "registrar dados em tempo de compilação"

    • isso é muito difícil dependendo do nível de determinismo externo que você deseja, porque a compilação incremental sob demanda não resultará necessariamente em uma saída consistente

  • especificamente, não tocaríamos em nada que existe atualmente e usa globals / C FFI

EDIT : apenas para que a discussão não descarrile, por exemplo:

Jogar fora os efeitos colaterais pode fazer com que o código se comporte de maneira diferente (digamos, escrever e depois ler um arquivo) se for chamado de const ou não const.

Todos nós podemos (provavelmente?) assumir que @oli-obk falou errado sobre jogar fora efeitos colaterais como esse.

Talvez tal recurso possa ser implementado como um sinalizador de configuração de nível de caixa que pode ser adicionado na raiz da caixa

Esse é um subconjunto do segundo exemplo de sugestões anteriores, de https://github.com/rust-lang/rust/issues/24111#issuecomment -376829588.
Se tivermos um "sinalizador de configuração" com escopo definido, o usuário poderá escolher escopos mais refinados IMO.

Que tal fazer ao contrário?
Em vez de ter const fn, tenha o lado fn.
Ainda é explícito (você precisa colocar o lado fn para chamar o lado fn, quebrando explicitamente a compatibilidade) e remove a desordem. (Alguns) intrínsecos e qualquer coisa com asm seria um lado fn.

Em https://github.com/rust-lang/rust/issues/24111#issuecomment -376829588 eu tentei apontar que bibliotecas inteiras poderiam ser "all const fn " ou "all side fn ".
Se não fosse em declarações de função, mas sim com escopo, talvez pudesse funcionar em uma edição futura.
No entanto, sem a semântica "inferir do corpo", você precisa projetar as interações de traços mesmo para o opt-in side fn , então você não está ganhando nada e está introduzindo um atrito potencialmente massivo.

A seção 3.3 do artigo "Singletons considerados prejudiciais" de Kenton Varda parece relevante aqui (honestamente, vale a pena ler a coisa toda).

E quanto ao registro de depuração?

Na prática, todos reconhecem que o log de depuração deve estar disponível para cada parte do código. Abrimos uma exceção para isso. A base teórica exata para essa exceção, para quem cuida, pode ser fornecida de algumas maneiras.

Do ponto de vista da segurança, o log de depuração é um singleton benigno. Ele não pode ser usado como um canal de comunicação porque é somente gravação. E é claramente impossível causar qualquer tipo de dano gravando em um log de depuração, já que o log de depuração não é um fator para a correção do programa. Mesmo que um módulo malicioso "spam" o log, as mensagens desse módulo podem ser facilmente filtradas, já que os logs de depuração normalmente identificam exatamente qual módulo produziu cada mensagem (às vezes eles até fornecem um rastreamento de pilha). Portanto, não há problema em fornecê-lo.

Argumentos análogos podem ser feitos para mostrar que o log de depuração não prejudica a legibilidade, a testabilidade ou a manutenção.

Outra justificativa teórica para o log de depuração diz que a função de log de depuração é realmente apenas um no-op que acontece de ser observado pelo depurador. Quando nenhum depurador está em execução, a função não faz nada. A depuração em geral obviamente quebra todo o modelo de capacidade de objeto, mas também é obviamente uma operação privilegiada.

Minha declaração sobre "podemos descobrir uma solução [para depuração]" estava realmente se referindo a uma API futura em potencial, que pode ser chamada de consts, mas tem alguma forma de impressão. Implementar aleatoriamente operações de impressão específicas da plataforma (apenas para que possamos tornar o código existente com instruções de impressão/depuração seja const) não é algo que um avaliador const deve fazer. Isso seria puramente opt-in, explicitamente não tendo comportamento observável diferente (por exemplo, avisos em const eval e saída de linha de comando/arquivo em tempo de execução). A semântica exata é deixada para futuras RFCs e deve ser considerada totalmente ortogonal para const fn em geral

Existem desvantagens significativas para const impl Trait e T: const Trait ?

Além de ainda mais pulverização em torno de const , apenas alguns métodos de características podem ser const, o que exigiria um controle mais refinado. Eu não conheço uma sintaxe legal para especificar isso. Talvez where <T as Trait>::some_method is const ( is pode ser uma palavra-chave contextual).

Novamente, isso é ortogonal a const fn.

[u8; SizeOf<T>::Output]

Se const e side fns forem separados, devemos levar em consideração as considerações reais do projeto. A maneira mais fácil de separá-los é fazer const fns uma extensão para algo que temos hoje - o sistema do tipo turing-complete.

Alternativamente, faça const fns realmente const: qualquer coisa const fn deve ser avaliada como se cada parâmetro fosse um const genérico.

Isso os torna muito mais fáceis de raciocinar, porque eu não posso raciocinar pessoalmente sobre const fns como eles estão atualmente. Eu posso raciocinar sobre tipos turing-completos, macros, fns normais, etc, mas acho impossível raciocinar sobre const fn, já que mesmo pequenos detalhes mudam completamente seu significado.

uma vez que mesmo pequenos detalhes mudam completamente de significado.

Você poderia detalhar? Você quer dizer extensões como const fn ponteiros, const limites de traço, ...? Porque não vejo nenhum detalhe menor na proposta const fn .

Alternativamente, faça const fns realmente const: qualquer coisa const fn deve ser avaliada como se cada parâmetro fosse um const genérico.

Isso é o que estamos fazendo em tempo de compilação. Só que em tempo de execução a função é usada como qualquer outra função.

O problema é que qualquer pequeno detalhe pode transformar uma avaliação const em uma avaliação de tempo de execução. Isso pode não parecer grande coisa, a princípio, mas pode ser .

Digamos que a chamada da função seja muito longa porque é tudo const fns? E você quer dividi-lo em várias linhas.

Então você adiciona alguns let s.

Agora seu programa demora 20x mais para ser executado.

@SoniEx2 O tamanho dos arrays ( $N em [u8; $N] ) é sempre avaliado em tempo de compilação. Se essa expressão não for const , a compilação falhará. Por outro lado, let x = foo() chamará foo em tempo de execução, seja ou não um const fn (módulo de inlining e propagação constante do otimizador, mas isso é totalmente separado de const fn ). Se você quiser nomear o resultado da avaliação de alguma expressão em tempo de compilação, você precisa de um item const .

Agora seu programa demora 20x mais para ser executado.

Não é assim que const fn funciona!

Se você declarar uma função const fn e adicionar uma ligação let dentro dela, seu código para de compilar.

Se você remover const de um const fn , é uma alteração importante e quebrará todos os usos dessa função dentro, por exemplo const , static ou comprimentos de array. Seu código que era código de tempo de execução e executou const fn , nunca seria executado em tempo de compilação. É apenas uma chamada de função de tempo de execução normal, então não fica mais lenta.

Edit: @SimonSapin me venceu :D

Const fn é avaliado em tempo de compilação, se possível.

Isso é,

const fn random() -> i32 {
    4
}

fn thing() -> i32 {
    let i = random(); // the RHS of this binding is evaluated at compile-time, there is no call to random at runtime.
}

Agora digamos que você tenha um const fn que recebe argumentos. Isso seria avaliado em tempo de compilação:

fn thing() {
    let x = const_fn_with_1_arg(const_fn_returns_value());
}

Isso faria com que const_fn_with_1_arg fosse avaliado em tempo de execução:

fn thing() {
    let x = const_fn_returns_value();
    let y = const_fn_with_1_arg(x); // suddenly your program takes 20x longer to run, and compiles 20x faster.
}

@eddyb Gostaria de saber se a preocupação com o design pode ser resolvida pela observação de que o "minimal const fn" é compatível com todas as possíveis extensões futuras? Ou seja, meu entendimento é que queremos estabilizar

marcando funções livres e métodos inerentes como const, permitindo que sejam chamados em contextos constantes, com argumentos constantes.

Isso parece ser totalmente compatível com qualquer projeto de "efeitos const para características". Também é compatível com o design "const inferido", porque podemos tornar const opcional mais tarde.

Existem projetos futuros alternativos que sejam incompatíveis com a atual proposta de "const fn mínimo"?

@nrc

Observe que o rfcbot não registrou sua preocupação everything-const (uma preocupação por comentário!) No entanto, parece ser um subconjunto da preocupação de design, que é abordada pelo meu comentário anterior (TL;DR: atual proposta mínima é totalmente compatível com tudo, poderíamos tornar a palavra-chave const opcional no futuro).

Quanto à preocupação com prioridade/consequências, gostaria de documentar o que discutimos em todas as mãos e o que ainda não documentamos:

  • const fn foo(x: i32) -> i32 { body } é uma adição relativamente menor sobre const FOO: i32 = body; , então o risco de precipitação é pequeno. Ou seja, a maior parte do código que realmente implementa const fn já está trabalhando duro no compilador estável (disclaimer: isso é algo que ouvi de @oli-obk, posso ter ouvido errado).

  • grupo de trabalho incorporado quer const fn mal :)

Além disso, observe que não estabilizar const fn leva à proliferação de APIs subótimas nas bibliotecas, porque elas precisam usar truques como ATOMIC_USIZE_INIT para contornar a falta de const fns.

deixe i = aleatório(); // o RHS desta ligação é avaliado em tempo de compilação, não há chamada para random em tempo de execução.

Não, isso não está acontecendo de forma alguma. Isso pode estar acontecendo (e llvm provavelmente está fazendo isso), mas você não pode esperar que nenhuma otimização do compilador que dependa de heurística realmente ocorra. Se você quiser que algo seja computado em tempo de compilação, coloque-o em um const e você terá essa garantia.

então const fn só é avaliado em um const, e isso é basicamente inútil de outra forma?

por que não ter const e non-const fn estritamente separados então?

veja que a semântica é uma bagunça porque eles intencionalmente misturam tempo de compilação e tempo de execução.

então const fn só é avaliado em um const, e isso é basicamente inútil de outra forma?

Eles não são inúteis, eles são executados em tempo de execução como qualquer outra função. Isso significa que você não precisa usar "sublinguagens" diferentes do Rust, dependendo de estar em uma avaliação const ou não.

por que não ter const e non-const fn estritamente separados então?

Toda a motivação para const fn é não ter essa separação. Caso contrário, precisaríamos duplicar todos os tipos de funções: AtomicUsize::new() + AtomicUsize::const_new() , mesmo que ambos os corpos sejam idênticos.

Você realmente quer escrever 90% de libcore duas vezes, uma para const eval e outra para runtime? O mesmo provavelmente vale para muitas outras caixas.

Eu estava pensando AtomicUsize::Of<value> . E sim, prefiro ter que escrever tudo duas vezes do que ter garantias desconhecidas. (Além disso, isso não se comportaria de maneira diferente com base no fato de algo estar sendo avaliado ou não.)

Você pode declarar consts em const fn para garantir a avaliação const (para const fn recursiva)? Ou você precisa passar por genéricos const? etc.

@SoniEx2 como um exemplo de como seu exemplo deve ser escrito para aproveitar const fn e se transformar em um erro de tempo de compilação se uma das funções se tornar não const :

fn thing() {
    const x: u32 = const_fn_returns_value();
    const y: u32 = const_fn_with_1_arg(x);
}

(exemplo de corrida completo no playground)

Um pouco menos ergonômico porque não há inferência de tipo, mas quem sabe isso pode mudar no futuro.

do que ter garantias desconhecidas.

você seria tão gentil e daria alguns exemplos de onde você acha que algo não está claro?

Você pode declarar consts em const fn para garantir a avaliação const (para const fn recursiva)? Ou você precisa passar por genéricos const? etc.

O objetivo de const fn não é avaliar magicamente as coisas em tempo de compilação. É ser capaz de avaliar as coisas em tempo de compilação.

Avaliar magicamente as coisas em tempo de compilação já está acontecendo desde que o rustc foi baseado em llvm. Então... exatamente quando parou de ser implementado no ocaml. Eu não acho que alguém queira remover a propagação constante do rustc.

const fn não influencia de forma alguma a propagação constante. Se você tivesse uma função que acidentalmente pudesse ser propagada por const, e o llvm o fizesse, e você alterasse essa função de uma forma que não fosse mais propagável por const, o llvm pararia de fazê-lo. Isso é completamente independente de anexar const a uma função. Para llvm não há diferença entre const fn e fn .

Ao mesmo tempo, rustc não altera seu comportamento quando você anexa const a um fn (assumindo que a função é uma const fn válida e, portanto, ainda compila depois de fazer isso). Ele só permite que você chame essa função em constantes a partir de agora .

Não estou pensando em LLVM, estou pensando em ferrugem. LLVM não importa para mim aqui.

@SoniEx2

Const fn é avaliado em tempo de compilação, se possível.

Isso não está correto. const fn , quando chamado em um contexto específico, será avaliado em tempo de compilação. Se isso não for possível, há um erro difícil. Em todos os outros contextos, a parte const não importa.

Exemplos de tais contextos que requerem const fn são comprimentos de array. Você pode escrever [i32; 15] . Você também pode escrever [i32; 3+4] porque o compilador pode calcular o 7 . Você não pode escrever [i32; read_something_from_network()] , porque isso faria algum sentido? Com esta proposta, você PODE escrever [i32; foo(15)] se foo for const fn , o que garante que seja mais como adição e menos como acesso à rede.

Não se trata de código que pode ou será executado quando o programa for executado. Não há "talvez avaliar em tempo de compilação". Há apenas "tem que avaliar em tempo de compilação ou abortar a compilação".

Leia também o RFC: https://github.com/rust-lang/rfcs/blob/master/text/0911-const-fn.md

Em vez de ter uma anotação const fn , e se fosse uma propriedade inferida? Não seria explícito no código-fonte, mas poderia ser rotulado automaticamente como tal na documentação gerada automaticamente. Isso permitiria uma eventual ampliação do que é considerado const , sem que os autores da biblioteca precisassem alterar seu código. A princípio, a inferência poderia ser limitada a qualquer const fn s atualmente suportado (funções puras e determinísticas sem quaisquer ligações let?).

Sob essa abordagem, a avaliação ocorreria em tempo de compilação se o resultado estivesse vinculado a uma variável const e, caso contrário, em tempo de execução. Isso parece mais desejável, pois dá ao chamador (e não ao chamado) controle sobre quando a função é avaliada.

Isso já foi bastante discutido. A desvantagem dessa abordagem é que torna mais fácil ir acidentalmente na outra direção - alguém pode estar usando uma função de biblioteca em um contexto const , mas o autor da biblioteca pode não mais const sem nem perceber.

Hum, sim, isso é um problema...

Edit: A única solução em que posso pensar, sem ir até const fn , seria ter uma anotação de desativação, para que o autor da biblioteca possa reservar o direito de quebrar const ness. Não tenho certeza se isso é melhor do que espalhar const fn em todos os lugares, no entanto. O único benefício real seria a adoção mais rápida de uma definição ampliada de const .

Editar 2: suponho que isso quebraria a compatibilidade com versões anteriores, por isso não é inicial. Desculpe pelo desvio.

Então... a discussão acabou. Vamos resumir:

o comentário do rfcbot é https://github.com/rust-lang/rust/issues/24111#issuecomment -376649804

Preocupações atuais

  • Isso não é uma prioridade, e o lançamento de 2018 já colocou o suficiente em nossos pratos
  • Começamos a marcar tudo const , o que é chato
  • É este o design com o qual queremos nos comprometer?

Eu realmente não posso falar sobre a coisa de prioridade + preocupação com as consequências, exceto que const fn tem assado todas as noites por um longo tempo

Os outros dois pontos estão intimamente relacionados. O design do @eddyb (https://github.com/rust-lang/rust/issues/24111#issuecomment-376829588 como eu entendi) é não ter const fn , mas ter um #[const] atributo que você pode colocar nas coisas:

#[const]
mod foo {
    pub fn square(i: i32) -> i32 { i * i }
}
#[const]
fn bar(s: &str) -> &str i{ s }
#[const]
fn boo() -> fn(u32) -> u32 { meh }
fn meh(u: u32) -> u32 { u + 1 }

e isso vai recursivamente para o que estiver marcado com ele, então qualquer função dentro de um módulo #[const] é um #[const] fn . Uma função declarada dentro de um #[const] fn também é um #[const] fn .

Isso reduz o número de anotações necessárias, já que alguns engradados apenas colocam um #![const] no lib.rs e pronto.

Problemas que vejo com esse design (mas esses problemas também existem em const fn ):

  • pode exigir suporte de desativação, porque você pode querer declarar algumas poucas funções não const profundamente dentro de uma árvore de módulo #[const] .
  • os ponteiros de função para #[const] fn na posição do tipo argumento/retorno precisam ser #[const] fn ?

    • novamente, a desativação seria necessária

Precisamos pensar sobre essas coisas, então não projetamos um sistema que será incompatível com uma versão futura onde queremos poder chamar funções por meio de ponteiros de função durante uma avaliação const.

Observe que eu não propus um determinado projeto, mas apenas listei algumas direções plausíveis conhecidas.
A ideia original era um atributo generalizado "expor corpo da função", não limitado a const , mas existem muitas variações possíveis, e algumas delas podem até ser boas .

EDIT : (não quero esquecer disso) @solson estava me mostrando como Lean tem atributos como @pattern que derivam automaticamente várias coisas do corpo de uma função.

@oli-obk Acho que não devemos usar atributos, porque unsafe não usa um atributo.
Além disso, async atualmente também não. E se introduzirmos try fn dados try { .. } blocos, então temos outra coisa que não é baseada em atributos. Acho que devemos tentar ser o mais consistentes possíveis coisas que são como efeitos. usando atributos ou não. #[target_feature(..)] coloca uma ruga na consistência geral.

PS: Você pode usar const mod { .. } para obter o mesmo efeito que #![const] mais ou menos. Isso também pode se aplicar a try mod , async mod , unsafe mod .

Eu sempre me inclinarei a fazer coisas com tipos especiais.

struct SizeOf<T>;

impl<T> SizeOf<T> {
    const intrisic Result: usize;
}

é mais fácil aprender novos tipos usando a sintaxe existente do que aprender novos conceitos com a nova sintaxe.

e mais tarde podemos suportar o sistema de tipos em tempo de execução.

fn sq(v: i32) -> i32 {
    Square<v>::Result
}

tipos em tempo de compilação, genéricos const em tempo de compilação ou em tempo de execução.

Então... estou sugerindo ignorar o fato de que pode haver um design possivelmente melhor por aí, porque temos um design que é

  1. Fácil de raciocinar
  2. Compatível com qualquer design mais permissivo
  3. Historicamente tem sido usado em código instável com grande sucesso, tendo como principal reclamação a falta de recursos.
  4. Pode ser linted para sugerir adicionar a anotação a funções ainda não anotadas que tenham um corpo que permita adicioná-la.
  5. Eu pessoalmente, conforme postado na solicitação de mesclagem, vejo como o único design que podemos estabilizar sem outro período de teste de vários anos.
  6. Permite que o código seja compartilhado entre o runtime e o código const eval, em vez de exigir código personalizado para cada

e mais tarde podemos suportar o sistema de tipos em tempo de execução.

Essa é a digitação dependente, que está longe , enquanto chamar um const fn em tempo de execução funciona bem hoje.

@oli-obk E os traços? Eu não quero estabilizar const fn sem alguma idéia do que vamos fazer para métodos de traço que são const fn em apenas alguns dos impl s do traço.

@eddyb parece que eu deveria agilizar a escrita dos novos limites e métodos const então. :)

@Centril Meu ponto é que a proposta de atributo (usando uma palavra-chave ou não) resultaria em uma abordagem muito mais diferente para lidar com métodos de traço, e temos que comparar isso .
A abordagem const fn atual pode parecer simples e extensível, mas não quando realmente estendida.

consts genéricos + consts genéricos:

intrinsic const SizeOf<T>: usize;

const Pow<const V: usize>: usize = V*V;

@eddyb Tenho várias soluções em mente que são totalmente compatíveis com o design const fn. Eu vou escrever.

Woah, acabei de ver que faz dois anos que começou. Existe alguma data prevista de estabilização? Tenho um caixote que está quase disponível no estábulo porque espera que esta extensão se estabilize. :)

@rfcbot diz respeito a endereços de ponteiro de tempo de execução

Em uma questão diferente, surgiu a questão de saber se queremos transparência referencial de const fn , e o problema de endereços de ponteiros brutos sendo usados ​​como um oráculo de não-determinismo apareceu: https://github.com/rust- lang/rust/issues/49146#issuecomment -386727325. Existe uma solução descrita lá, mas envolve fazer algumas operações de ponteiro bruto unsafe (não tenho certeza de quantas delas são permitidas hoje), antes da estabilização.

@eddyb E0018 não se aplicaria a const fn s também?

A maneira C é que os ponteiros de objeto podem ser todos 0, a menos que sejam relativos (ou seja, dentro de um objeto) e rastreados em tempo de execução de alguma forma.

Não tenho certeza se ferrugem suporta as regras de alias de C.

@sgrif Muitos dos erros emitidos sobre constantes vão desaparecer mais cedo ou mais tarde - miri não se importa com o tipo que um valor é visto, um local abstrato que está em um usize ainda é um local abstrato (e convertê-lo em um ponteiro retorna o ponteiro original).

Acabei de verificar e, por enquanto , tanto os ponteiros de conversão para inteiros quanto os operadores de comparação entre ponteiros são proibidos em contextos constantes. No entanto, isso é exatamente o que pensamos , ainda estou com medo.

@eddyb Justo o suficiente. No entanto, eu esperaria que qualquer preocupação que você tenha que atingir const fn já atinja qualquer bloco const hoje.

@sgrif A diferença é que const (mesmo const s associados que dependem de parâmetros de tipo genérico) são totalmente avaliados em tempo de compilação, sob miri, enquanto const fn não é const fn para chamadas em tempo de execução.
Portanto, se realmente queremos transparência referencial, precisamos ter certeza de que não permitimos (em código seguro, pelo menos) coisas que possam causar não determinismo em tempo de execução, mesmo que estejam bem em miri .
O que provavelmente significa que obter os bits de float também é um problema, porque, por exemplo, cargas úteis de NaN.

Coisas que você deve considerar fazer em miri:

Todos os ponteiros são 0, a menos que sejam relativos. Por exemplo:

#[repr(C)]
struct X {
    a: usize,
    b: u8,
}
let x = X { a: 1, b: 2 };
let y: usize = 3;
assert_eq!(&x as *const _ as usize, 0);
assert_eq!(&x.a as *const _ as usize, 0);
assert_eq!(&x.b as *const _ as usize, 8);
assert_eq!(&y as *const _ as usize, 0);

Então você os acompanha na avaliação miri. Algumas coisas seriam UB, como ir de ponteiro para uso de volta para ponteiro, mas essas são fáceis de não permitir (banir uso para conversões de ponteiro, já que você já estaria rastreando ponteiros em tempo de execução/avaliação).

Quanto aos bits flutuantes, normalização NaN?

Eu acho que ambos tornariam a coisa toda determinística.

O que provavelmente significa que obter os bits de float também é um problema, porque, por exemplo, cargas úteis de NaN.

Acho que para transparência referencial total, teríamos que tornar todas as operações de float inseguras.

o determinismo de ponto flutuante é difícil

O ponto mais importante aqui é que o otimizador do LLVM pode alterar a ordem das operações float, bem como ~realizar~ operações de fusíveis para as quais tem um opcode combinado. Essas mudanças afetam o resultado da operação, mesmo que a diferença real seja pequena. Isso afeta a transparência referencial porque miri executa o mir não otimizado para llvm enquanto o destino executa o código nativo otimizado para llvm e possivelmente reordenado e, portanto, com uma semântica diferente da nativa.

Eu concordaria com um primeiro recurso const fn estável sem floats por enquanto até que haja uma decisão sobre a importância da transparência referencial, mas não gostaria que const fn fosse desacelerado ou bloqueado por essa discussão.

O ponto mais importante aqui é que o otimizador do LLVM pode alterar a ordem das operações float, bem como realizar operações de fusíveis para as quais possui um opcode combinado. Essas mudanças afetam o resultado da operação, mesmo que a diferença real seja pequena.

As operações de fusão (presumo que você se refira a mul-add) não são permitidas/não feitas sem sinalizadores de matemática rápida precisamente porque altera o arredondamento dos resultados. O LLVM é muito cuidadoso para preservar a semântica exata das operações de ponto flutuante ao direcionar hardware compatível com IEEE, dentro dos limites definidos pelo "ambiente de ponto flutuante padrão".

Duas coisas que o LLVM não tenta preservar são as cargas NaN e a sinalização dos NaNs, porque ambos não podem ser observados no ambiente fp padrão - apenas inspecionando os bits dos floats, que, portanto, precisaríamos proibir. (E mesmo que o LLVM tenha sido mais cuidadoso com isso, o hardware também varia em seu tratamento de cargas NaN, então você não pode fixar isso no LLVM.)

O outro caso importante que conheço onde a decisão do compilador pode fazer a diferença para os resultados de ponto flutuante é a localização de spills e recargas no código x87 (pré-SSE). E isso é principalmente um problema porque o x87 por padrão arredonda para 80 bits para resultados intermediários e arredonda para 32 ou 64 bits nas lojas. Definir corretamente o modo de arredondamento antes de cada instrução FPU para obter resultados corretamente arredondados é possível, mas não é realmente prático e, portanto, (acredito) o LLVM não o suporta.

Em plena transparência referencial

Minha opinião é que devemos ir com total transparência referencial porque combina com a mensagem geral da Rust de escolher segurança/correção sobre conveniência/completude .

A transparência referencial adiciona muitos benefícios de raciocínio, como permitir o raciocínio equacional (até o fundo, mas o raciocínio rápido e solto é moralmente correto ).

No entanto, é claro que existem desvantagens wrt. perdendo na completude wrt. CTFE. Com isso quero dizer que um mecanismo const fn referencialmente transparente não seria capaz de avaliar tanto em tempo de compilação quanto um esquema const fn não transparente para referência poderia. Daqueles que propõem não manter a transparência referencial, peço que forneçam o máximo possível de casos de uso concretos contra essa proposição para que possamos avaliar os trade-offs.

Ok, parece que você está certo sobre o ponto LLVM, de fato parece evitar operações computacionalmente erradas, a menos que você ative o modo matemático rápido.

No entanto, ainda há um monte de estados dos quais as operações de ponto flutuante dependem, como a precisão interna. O avaliador de float CTFE conhece o valor de precisão interna em tempo de execução?

Além disso, durante o derramamento de valores para a memória, convertemos o valor interno para o formato ieee754 e, portanto, alteramos a precisão. Isso também pode afetar o resultado, e o algoritmo de com compiladores executar derramamento não é especificado, não é?

@est31 Observe que eu assumi que não nos importamos se o comportamento do tempo de compilação e do tempo de execução for diferente, apenas que as chamadas repetidas (com gráficos de objetos congelados) sejam consistentes e livres de efeitos colaterais globais.

Portanto, se realmente queremos transparência referencial, precisamos ter certeza de que não permitimos (em código seguro, pelo menos) coisas que possam causar não determinismo em tempo de execução, mesmo que estejam bem sob miri.

Portanto, o objetivo aqui é garantir que um const fn seja determinístico e livre de efeitos colaterais em tempo de execução, mesmo que o miri erre durante a execução , pelo menos se o const fn for totalmente seguro código? Eu nunca considerei isso importante, TBH. É um requisito bastante forte e, de fato, teríamos que pelo menos tornar ptr-to-int e float-to-bits inseguros, o que será difícil de explicar. OTOH, devemos também tornar a comparação de ponteiro bruto insegura e eu ficaria feliz com isso: D

Qual é a motivação para isso? Parece que estamos tentando reintroduzir a pureza e um sistema de efeitos, que são coisas que Rust já teve e perdeu, presumivelmente porque eles não carregavam seu peso (EDIT: ou porque simplesmente não era flexível o suficiente para fazer todas as diferentes coisas para as quais as pessoas queriam usá-lo, veja https://mail.mozilla.org/pipermail/rust-dev/2013-April/003926.html).

ptr-to-int é seguro se converter desde o início de um objeto e você permitir que int-to-ptr falhe. seria 100% determinístico. e cada chamada produziria os mesmos resultados.

ptr-to-int é seguro, int-to-ptr é inseguro. ptr-to-int deve permitir resultados "inesperados" na avaliação const .

e você realmente deve usar a canonização NaN em um interpretador/VM. (um exemplo disso é LuaJIT, que faz com que todos os resultados NaN sejam o NaN canônico, e todos os outros NaN sejam um NaN empacotado)

ptr-to-int é seguro se converter desde o início de um objeto e você permitir que int-to-ptr falhe. seria 100% determinístico. e cada chamada produziria os mesmos resultados.

Como você sugere que implementemos isso em tempo de execução? (&mut *Box::new(...)) as usize é uma função não determinística agora e deve ser tudo const fn . Então, como você sugere que tenhamos certeza de que é "transpent referencialmente em tempo de execução" no sentido de sempre retornar o mesmo valor?

Box::new deve retornar um ponteiro rastreado na VM.

A conversão resultaria em 0, em tempo de compilação (ou seja, em uma avaliação const). Na avaliação não const, retornaria qualquer coisa.

Um ponteiro rastreado funciona assim:

Você aloca um ponteiro e o atribui. Quando você o atribui, a VM define o valor do ponteiro como 0, mas pega o endereço de memória do ponteiro e o anexa a uma tabela de pesquisa. Ao usar o ponteiro, você passa pela tabela de pesquisa. Quando você obtém o valor do ponteiro (ou seja, os bytes que o compõem), obtém 0. Assumindo que é o ponteiro base de um objeto.

Box::new deve retornar um ponteiro rastreado na VM.

Estamos falando de comportamento em tempo de execução aqui. Como em, o que acontece quando isso é executado no binário. Não há VM.

miri já pode lidar com tudo isso muito bem e inteiramente deterministicamente.


Além disso, voltando à preocupação com const fn e determinismo de tempo de execução: Se quisermos isso (o que não tenho certeza de que queremos, ainda esperando que alguns me expliquem por que nos importamos :D ), poderíamos apenas declarar ptr-to-int e float-to-bits sejam operações não const. Existe algum problema com isso?

@RalfJung Eu vinculei https://github.com/rust-lang/rust/issues/49146#issuecomment -386727325 já no comentário de preocupação, talvez isso tenha se perdido em outras mensagens - inclui uma descrição de uma solução que o @Centril propôs ( faça as operações unsafe e coloque na lista branca alguns "usos corretos", por exemplo, offset_of para ptr-to-int e float-to-bits normalizados por NaN).

@eddyb claro, existem várias soluções; o que eu estou pedindo é a motivação. Também não encontrei por lá.

Além disso, citando-me do IRC: Acho que podemos até argumentar com razão que o CTFE é determinista, mesmo quando a conversão ptr-to-int é permitida. Ainda é necessário um código não CTFE (por exemplo, extrair bits de um ptr convertido em um int) para realmente observar qualquer não determinismo.

const fn thing() -> usize {
    (*Box::new(0)) as *const _ as usize
}

const X: usize = thing();
const Y: usize = thing();

é X == Y?

com minha sugestão, X == Y.

@SoniEx2 que quebra foo as *const _ as usize as *const _ que é totalmente um noop agora

Motivação escrita:
Eu pessoalmente não vejo um problema com o comportamento de tempo de execução diferente do comportamento de tempo de compilação ou mesmo const fn sendo não determinístico em tempo de execução. Segurança não é o problema aqui, então inseguro é totalmente a palavra-chave errada imo. Este é apenas um comportamento surpreendente que estamos pegando totalmente em tempo de compilação eval. Você já pode fazer isso em funções normais, então não há surpresas. Const fn é sobre marcar funções existentes como avaliáveis ​​em tempo de compilação sem afetar o tempo de execução. Não é sobre pureza, pelo menos essa é a vibe que eu tenho quando as pessoas falam sobre o "inferno puro" (eu não estava por perto, então posso estar interpretando mal)

@SoniEx2 Sim, X == Y sempre.

No entanto, se você tiver:

const fn oracle() -> bool { let x = 0; let y = &x as *const _ as usize; even(y) }

fn main() {
    assert_eq!(oracle(), oracle());
}

Então main pode entrar em pânico em tempo de execução.

@RalfJung

ou porque simplesmente não era flexível o suficiente para fazer todas as coisas diferentes para as quais as pessoas queriam usá-lo [..]

O que eram essas coisas? É difícil avaliar isso sem concreção.

Acho que podemos até argumentar com razão que o CTFE é determinista, mesmo quando a conversão ptr-to-int é permitida.
[...]
Ainda é necessário um código não CTFE (por exemplo, extrair bits de um ptr convertido em um int) para realmente observar qualquer não determinismo.

Sim, const fn ainda é determinístico quando executado em tempo de compilação, mas não acredito que seja determinístico em tempo de execução porque invariavelmente haverá alguns não- const fn (apenas fn ) se o resultado de const fn for útil para qualquer coisa em tempo de execução, e então esse código fn observará os efeitos colaterais do const fn executado.

Todo o ponto de separação entre código puro e não puro em Haskell é que você pode tomar a decisão "deve haver efeitos colaterais" local, onde você não precisa pensar sobre possíveis efeitos colaterais globalmente. Ou seja, dado:

reverse :: [a] -> [a]
reverse []       = []
reverse (x : xs) = reverse xs ++ [x]

putStrLn :: String -> IO ()
getLine :: IO String

main :: IO ()
main = do
    line <- getLine
    let revLine = reverse line
    putStrLn revLine

Você sabe que o resultado de revLine em let revLine = reverse line só pode depender do estado passado para reverse que é line . Isso fornece benefícios de raciocínio e separação limpa.

Gostaria de saber como era o sistema de efeitos naquela época ... Acho que precisamos de um dado const fn , async fn , etc. de qualquer maneira para fazê-lo de forma limpa e obter a reutilização de código (algo https:// github.com/rust-lang/rfcs/pull/2237 mas com algumas mudanças...) e parametricidade parece uma boa ideia para isso.

Nas palavras imortais de Phil Wadler e Conor McBride:

Devo ser puro ou impuro?
—Philip Wadler [60]

Dizemos 'Sim': a pureza é uma escolha a fazer localmente

https://arxiv.org/pdf/1611.09259.pdf

@oli-obk

Eu não invejo a pessoa que tem que documentar esse comportamento surpreendente que o resultado da execução pode diferir por const fn se executado em tempo de execução ou em tempo de compilação com os mesmos argumentos.

Como opção conservadora, proponho que adiemos a decisão sobre transparência/pureza referencial estabilizando const fn como referencialmente transparente, tornando unsafe (ou não const ) para violar mas não garantimos transparência referencial, então as pessoas também não podem assumir.

Mais tarde, quando tivermos adquirido experiência, poderemos tomar a decisão.
Ganhar experiência inclui pessoas que tentam fazer uso do não-determinismo, mas falham, e então relatam e nos contam sobre seus casos de uso.

Sim, mas isso não é uma avaliação const.

Converter int para ptr em const não parece uma grande perda. Obter deslocamentos de campo parece mais importante, e é isso que estou tentando preservar.

Eu não gostaria de mudar silenciosamente o comportamento ao adicionar const a uma função @SoniEx2 e isso é essencialmente o que sua sugestão faria.

@centril eu concordo, vamos fazer a coisa conservadora agora. para não torná-los inseguros, mas instáveis. desta forma o libstd pode usá-lo, mas podemos decidir mais tarde sobre os detalhes.

Um problema que temos é que os usuários sempre podem bloquear qualquer análise que fizermos introduzindo uniões e fazendo algumas conversões sofisticadas, então teríamos que fazer um UB de conversões tão contornado para podermos alterá-lo mais tarde.

ainda seria o mesmo em tempo de execução, o const só mudaria as coisas em tempo de const, o que... não seria possível nem fazer sem o const.

não há problema em const fns ser uma sublinguagem com diferentes regras de alias de ponteiro.

@Centril @est31 Não tenho certeza do que o clorotrifluoroetileno tem a ver com alguma coisa (podemos evitar usar um monte de siglas sem defini-las, por favor?)

@sgrif CTFE (avaliação de função em tempo de compilação) tem sido usado há muito tempo para Rust (inclusive nas partes antigas deste tópico). É um daqueles termos que já devem ser definidos em algum lugar central.

@sgrif lol desculpe na maioria das vezes eu sou a pessoa que está se perguntando "que palavras estranhas eles estão usando agora?". Acho que estava conversando com @eddyb no IRC quando ele mencionou "CTFE" e tive que perguntar a ele o que significava :p.

@eddyb ctrl+f + CTFE nesta página não leva a nada que o defina. De qualquer forma, estou apenas partindo do pressuposto de que não queremos forçar as pessoas a cavar muito para participar das discussões aqui. "Você já deve saber o que isso significa" é IMO bastante excludente.

@Centril @est31 obrigado :heart:

Então... qualquer pensamento sobre

Um problema que temos é que os usuários sempre podem bloquear qualquer análise que fizermos introduzindo uniões e fazendo algumas conversões sofisticadas, então teríamos que fazer um UB de conversões tão contornado para podermos alterá-lo mais tarde.

Os acessos de campo de união dentro de const fn devem ser instáveis ​​também?

ctrl+f + CTFE nesta página não leva a nada que o defina.

Desculpe, o que eu quis dizer com isso é que seu uso no design e desenvolvimento do Rust é anterior a esta edição anterior à 1.0.
Deveria haver um documento central para tais termos, mas, infelizmente, o glossário da referência está realmente em falta.
HRTB e NLL são dois exemplos de outras siglas que são mais recentes, mas também não explicadas em linha.

Não quero que nenhuma discussão seja excludente, mas não acho justo pedir ao @Centril ou ao @est31 para definir CTFE, já que nenhum deles introduziu o acrônimo em primeiro lugar.
Existe uma solução que infelizmente não funcionaria necessariamente no GitHub, que eu vi em um certo subreddit, onde um bot criará um comentário de nível superior e o manterá atualizado , com uma lista de expansões para todas as siglas comumente usadas desse subreddit aparecendo na discussão.

Além disso, estou curioso se estou em uma bolha do Google, já que os dois artigos da wikipedia para CTFE ("Clorotrifluoroetileno" e "Execução da função de tempo de compilação") são os dois primeiros resultados para mim. Mesmo assim, o primeiro começa com "Para o recurso do compilador, consulte a execução da função de tempo de compilação.".

( Coloquei o link wiki CTFE no topo; agora podemos voltar para const fn s? :P)

Alguém poderia adicionar CTFE ao glossário do guia rustc (estou no celular)?

Além disso, acho que devemos estabilizar o mínimo e ver com o que as pessoas se deparam muito na prática, o que é uma abordagem atual com expressões const if e match.

@Centril

Eu não invejo a pessoa que tem que documentar esse comportamento surpreendente de que o resultado da execução pode diferir para const fn se executado em tempo de execução ou em tempo de compilação com os mesmos argumentos.

O even que você escreveu acima falharia se executado em tempo CTFE porque inspeciona os bits de um ponteiro. (Embora, na verdade, possamos dizer deterministicamente que isso ocorre por causa do alinhamento, e se o teste de uniformidade for feito por meio de operações de bits, "miri completo" faria isso corretamente. Mas vamos supor que você esteja testando todo o byte menos significativo, não apenas o byte última parte.)
O caso sobre o qual estamos falando aqui é uma função que comete erros com um erro de intérprete em tempo de compilação, mas é bem-sucedida em tempo de execução. A diferença está em se a função ainda completa a execução. Não acho difícil explicar.

Concordo que, se CTFE for bem-sucedido com um result , a versão em tempo de execução da função também deve ser garantida para obter o mesmo resultado. Mas essa é uma garantia muito mais fraca do que estamos falando aqui, não é?

@oli-obk

Concordo, vamos fazer a coisa conservadora agora. para não torná-los inseguros, mas instáveis. desta forma o libstd pode usá-lo, mas podemos decidir mais tarde sobre os detalhes.

Perdi o contexto, o que são "estes" aqui?

Agora, felizmente, CTFE miri simplesmente se recusa a fazer qualquer coisa com valores de ponteiro - aritmética, comparação, tudo erros. Esta é uma verificação feita em tempo CTFE com base nos valores realmente usados ​​na computação, não pode ser contornada por uniões e de qualquer forma o código que seria necessário para fazer a aritmética/comparação simplesmente não existe . Portanto, tenho certeza de que satisfazemos a garantia que declarei acima.

Eu poderia imaginar problemas se tivéssemos CTFE retornar um valor de ponteiro, mas como um valor de ponteiro calculado em tempo de compilação faria algum sentido em qualquer lugar? Suponho que já verificamos o que o miri computa para não conter valores de ponteiro porque temos que transformá-lo em bits?

Poderíamos adicionar cuidadosamente operações ao CTFE miri e, na verdade, tudo o que precisamos para o offset_of de @eddyb [1] é a subtração do ponteiro. Esse código existe em "full miri", e só tem sucesso se os dois ponteiros estiverem dentro da mesma alocação, o que é suficiente para manter a garantia acima. O que não funcionaria é o assert que @eddyb adicionou como salvaguarda.
Também podemos permitir operações de bits em valores de ponteiro se as operações afetarem apenas a parte alinhada do ponteiro, isso ainda é determinístico e o código realmente já existe em "miri completo".

EDIT: [1] Para referência, estou me referindo à macro dele neste tópico que não podemos vincular porque foi marcada como fora do tópico, então aqui está uma cópia:

macro_rules! offset_of {
    ($Struct:path, $field:ident) => ({
        // Using a separate function to minimize unhygienic hazards
        // (e.g. unsafety of #[repr(packed)] field borrows).
        // Uncomment `const` when `const fn`s can juggle pointers.
        /*const*/ fn offset() -> usize {
            let u = $crate::mem::MaybeUninit::<$Struct>::uninit();
            // Use pattern-matching to avoid accidentally going through Deref.
            let &$Struct { $field: ref f, .. } = unsafe { &*u.as_ptr() };
            let o = (f as *const _ as usize).wrapping_sub(&u as *const _ as usize);
            // Triple check that we are within `u` still.
            assert!((0..=$crate::mem::size_of_val(&u)).contains(&o));
            o
        }
        offset()
    })
}

EDIT2: Na verdade, ele também postou aqui .

"estes" são float -> conversão de bits e ponteiro -> conversões de uso

Concordo que, se o CTFE for bem-sucedido com um resultado, a versão em tempo de execução da função também deve ser garantida para obter o mesmo resultado. Mas essa é uma garantia muito mais fraca do que estamos falando aqui, não é?

Então, uma função chamada com argumentos em tempo de execução só tem pureza garantida se realmente terminar se avaliada em tempo de compilação com os mesmos argumentos?

Isso tornaria nossas vidas um milhão de vezes mais fáceis, especialmente porque não vejo uma maneira de evitar o não-determinismo sem deixar brechas ou paralisar a const fn de maneiras inovadoras.

Então, uma função chamada com argumentos em tempo de execução só tem pureza garantida se realmente terminar se avaliada em tempo de compilação com os mesmos argumentos?

Sim, é isso que estou propondo.


Pensando um pouco mais (e lendo a resposta de @oli-obk que apareceu enquanto eu escrevia isso), tenho a sensação de que você quer uma garantia adicional ao longo das linhas de "um const fn seguro não cometerá erros em Tempo CTFE (exceto panics) quando chamado com argumentos válidos". Algum tipo de garantia de "segurança constante". Juntamente com a garantia que mencionei acima sobre o sucesso do CTFE coincidindo com o comportamento em tempo de execução, isso forneceria uma garantia de que um const fn seguro será determinístico em tempo de execução, porque corresponde à execução bem-sucedida do CTFE.

Concordo que é uma garantia mais difícil de obter. Para melhor ou pior, Rust tem várias operações seguras que o CTFE miri não pode garantir sempre executar com sucesso mantendo o determinismo, como a variante do oracle do @Centril que testa o byte menos significativo para ser 0. Da perspectiva de CTFE nesta configuração, os "valores válidos do tipo usize " constituem apenas valores que são PrimVal::Bytes , enquanto um PrimVal::Ptr não deve ser permitido [1]. É como se tivéssemos um sistema de tipos ligeiramente diferente no contexto const -- não estou propondo que alteremos o que miri faz, estou propondo que alteremos o que os vários tipos de Rust "significam" quando anexados a um const fn . Tal sistema de tipos garantiria que todas as operações aritméticas e de bits seguras não pudessem dar errado em CTFE: Para entradas válidas para CTFE, a subtração de inteiros nunca pode falhar em CTFE porque ambos os lados são PrimVal::Bytes . Claro, ptr-to-int tem que ser uma operação insegura nesta configuração porque seu valor de retorno tem o tipo usize mas não é um usize válido para CTFE.

Se esta é uma garantia com a qual nos preocupamos, não me parece irracional tornar o sistema de tipos mais rigoroso ao verificar as funções CTFE; afinal, queremos usá-lo para fazer mais coisas (verificando "const safety"). Eu acho que faria muito sentido, então, declarar ptr-to-int lança unsafe no contexto const , argumentando que isso é necessário porque o contexto const faz garantias adicionais.

Assim como nossa "segurança de tempo de execução" normal pode ser subvertida por código inseguro, também pode "segurar const", e tudo bem. Não vejo nenhum problema com o código inseguro ainda poder fazer conversões ptr-to-int por meio de uniões - isso é código inseguro , afinal. Neste mundo, as obrigações de prova para código inseguro em um contexto const são mais fortes do que aquelas em um contexto não const; se sua função retornar um inteiro, você deve provar que isso sempre será um PrimVal::Bytes e nunca será um PrimVal::Ptr .

A macro de @eddyb precisaria de um bloco inseguro, mas ainda seria seguro usar porque só faz uso desses "recursos inseguros const" (ptr-to-usize e depois subtrai o resultado, ou apenas usando a subtração intrínseca do ponteiro diretamente) de forma que seja garantido não gerar um erro CTFE.

O custo de tal sistema seria que um const fn de ordem superior segura deve ter um fechamento const fn para poder garantir que a execução do fechamento não violará, por si só, a "segurança constante". Esse é o preço de realmente obter garantias adequadas sobre const fn .

[1] Estou ignorando totalmente os floats aqui, pois não sei muito sobre onde o não-determinismo surgiria. Alguém pode fornecer um exemplo de código de ponto flutuante que se comportaria de maneira diferente em tempo de CTFE do que em tempo de execução? Seria suficiente, por exemplo, fazer um erro de miri se, ao fazer uma operação de ponto flutuante, um dos operandos fosse um NaN sinalizador (para obter a primeira garantia, a do meu post anterior), e dizer que o sistema do tipo CTFE não permite sinalizar NaNs no tipo f32 / f64 (para obter "segurança const")?

O que não funcionaria é a afirmação que @eddyb adicionou como salvaguarda.

Claro, mas você pode reescrever assert!(condition); para [()][!condition as usize]; por enquanto.

Claro, mas você pode reescrever assert!(condition); para [()][!condição como usize]; por enquanto.

Não é a afirmação que eu estava pensando, é o teste de igualdade de ponteiro em sua condição. A igualdade de ponteiro é ruim e eu preferiria que não pudéssemos permitir isso em CTFE.

EDIT: Não importa, acabei de perceber que o assert testa o deslocamento. Então, na verdade, ele nunca pode falhar em tempo CTFE porque se os ponteiros não estiverem no mesmo bloco ao fazer wrapping_sub , o miri irá errar.

// guess we can't have this as const fn
fn is_eq<T>(a: &T, b: &T) -> bool {
    a as *const _ == b as *const _
}

como eu disse antes, use ponteiros virtuais em miri, em vez de ponteiros reais. ele pode fornecer determinismo const no tempo const. se a função for escrita corretamente, o comportamento de tempo de execução e tempo de compilação deve produzir os mesmos resultados, independentemente de o tempo de execução ser não determinístico enquanto o tempo de compilação é determinístico. você pode ter um comportamento determinístico em ambientes não determinísticos se codificar para isso.

obter um deslocamento de campo é determinístico. com ponteiros virtuais, permanece determinístico. com ponteiros reais, ainda é determinístico.

obter a igualdade do 14º bit de um ponteiro não é determinístico. com ponteiros virtuais, torna-se determinístico. com ponteiros reais, não é determinístico. isso é bom, porque um está acontecendo em tempo de compilação (um ambiente determinístico), enquanto o outro está acontecendo em tempo de execução (um ambiente não determinístico).

const fn deve ser tão determinista quanto o ambiente em que está sendo usado.

@SoniEx2

// guess we can't have this as const fn

Na verdade não podemos. Acho que poderia viver com uma versão de comparação de ponteiro bruto que apresenta erros se qualquer ponteiro não for atualmente desreferenciado no sentido de estar dentro de um objeto alocado (mas não é isso que "miri completo" implementa atualmente). No entanto, isso ainda tornaria is_eq não "const safe" porque se T for de tamanho zero, poderia apontar um além do final de um objeto, mesmo se considerarmos apenas o código seguro.

C++ permite comparar ponteiros um após o final para produzir um resultado indeterminado (pense: não determinístico). Tanto C quanto C++ permitem comparar um ponteiro pendente para produzir um resultado indeterminado. Não está claro o que o LLVM garante, mas prefiro não apostar em garantias que excedam o que eles têm para garantir para C/C++ (o mais fraco dos dois, se forem diferentes). Isso é um problema se quisermos garantir o determinismo em tempo de execução para tudo que é executado com sucesso em CTFE, o que acho que fazemos.

@RalfJung

A diferença está em se a função ainda completa a execução.

Advogado do diabo: "retornar ⊥" em um caso é o mesmo que ter resultados diferentes.

Não acho difícil explicar.

Pessoalmente, vejo isso como um comportamento surpreendente; Você pode explicar, e eu posso entender (mas não sou representante...), mas não se encaixa na minha intuição.

@oli-obk

Então, uma função chamada com argumentos em tempo de execução só tem pureza garantida se realmente terminar se avaliada em tempo de compilação com os mesmos argumentos?

Pessoalmente, não acho esta garantia suficiente. Acho que devemos primeiro ver até onde podemos chegar com a pureza e somente quando soubermos que é incapacitante na prática devemos avançar para garantias mais fracas.

@RalfJung

isso forneceria uma garantia de que uma const fn segura será determinística em tempo de execução, porque corresponde à execução bem-sucedida do CTFE.

OK; Você me perdeu; Eu não vejo como você chegou a essa garantia "safe const fn is deterministic" dadas as duas premissas; poderia detalhar o raciocínio?

Se esta é uma garantia com a qual nos preocupamos, não me parece irracional tornar o sistema de tipos mais rigoroso ao verificar as funções CTFE; afinal, queremos usá-lo para fazer mais coisas (verificando "const safety"). Eu acho que faria muito sentido, então, declarar casts ptr-to-int inseguros no contexto const, argumentando que isso é necessário porque o contexto const oferece garantias adicionais.

Assim como nossa "segurança de tempo de execução" normal pode ser subvertida por código inseguro, também pode "segurar const", e tudo bem. Não vejo nenhum problema com o código inseguro ainda poder fazer conversões ptr-to-int por meio de uniões - afinal, esse é um código inseguro. Neste mundo, as obrigações de prova para código inseguro em um contexto const são mais fortes do que aquelas em um contexto não const; se sua função retornar um inteiro você tem que provar que isso sempre será um PrimVal::Bytes e nunca será um PrimVal::Ptr .

Esses parágrafos são música para os meus ouvidos! ❤️ Isso parece garantir o determinismo ("pureza")? e é precisamente a coisa que eu tinha em mente antes. Eu acho que segurança const também é um termo fantástico!

Para referência futura, deixe-me chamar essa garantia de "consistência do CTFE": Se o CTFE não apresentar erro, seu comportamento corresponde ao tempo de execução - ambos divergem ou ambos terminam com o mesmo valor. (Estou ignorando inteiramente os valores de retorno de ordem superior aqui.)

@Centril

Advogado do diabo: "retornar ⊥" em um caso é o mesmo que ter resultados diferentes.

Bem, isso é claramente uma questão de definição. Acho que você entendeu a garantia de solidez do CTFE que eu estava descrevendo e você concorda que é uma garantia que queremos; se é tudo o que queremos está em discussão :)

OK; Você me perdeu; Eu não vejo como você chegou a essa garantia "safe const fn is deterministic" dadas as duas premissas; poderia detalhar o raciocínio?

Digamos que temos alguma chamada para foo(x) onde foo é uma função const segura e x é um valor const-válido (ou seja, não é &y as *const _ as usize ). Então sabemos que foo(x) será executado em CTFE sem gerar um erro, por segurança const. Como consequência, pela solidez do CTFE, em tempo de execução foo(x) se comportará da mesma maneira que em CTFE.

Essencialmente, acho que decompus sua garantia em duas partes - uma garantindo que um const fn seguro nunca tentará fazer algo que o CTFE não suporta (como ler de stdin ou determinar se o byte menos significativo de um ponteiro é 0), e uma garantia de que o que o CTFE suporta corresponda ao tempo de execução.

Esses parágrafos são música para os meus ouvidos! coração Isso parece garantir o determinismo ("pureza")? e é precisamente a coisa que eu tinha em mente antes. Eu acho que segurança const também é um termo fantástico!

Estou feliz por ter gostado. :) Isso significa que finalmente entendi do que estamos falando aqui. "pureza" pode significar tantas coisas diferentes que muitas vezes me sinto um pouco desconfortável quando o termo é usado. E o determinismo não é condição suficiente para a segurança const, o critério relevante é se a execução em CTFE gera um erro. (Um exemplo de uma função determinista não-const-safe é minha variante de seu orcale multiplicado por 0. Isso não é correto mesmo usando código inseguro, pois miri irá errar ao inspecionar os bytes de um ponteiro, mesmo que os bytes não importem. É como a operação que extrai o byte menos significativo de um ponteiro é "const-UB" e, portanto, não é permitido mesmo em código const inseguro.)

sim, um ponteiro após o final de um elemento de matriz apontaria para o próximo elemento de matriz, provavelmente. E daí? não é realmente não determinístico? determinista em tempo de compilação é tudo o que importa de qualquer maneira. tanto quanto eu me importo, a avaliação do tempo de execução pode segfault para o esgotamento da pilha.

@RalfJung

O custo de tal sistema seria que um const fn seguro de ordem superior deve ter um encerramento const fn para poder garantir que a execução do encerramento não violará, por si só, a "segurança const". Esse é o preço de realmente obter garantias adequadas sobre const fn .

Eu acredito que isso pode ser severamente mitigado para suportar a transformação da maioria dos códigos existentes, introduzindo ?const onde você pode escrever funções de ordem superior cujo resultado pode ser vinculado a const se e somente se a função fornecida for ?const fn(T) -> U e onde is_const(x : T) ; Então você tem:

?const fn twice(fun: ?const fn(u8) -> u8) { fun(fun(42)) }

fn id_impure(x: u8) -> u8 { x }
const fn id_const(x: u8) -> u8 { x }
?const fn id_maybe_const(x: u8) -> u8 { x }

fn main() {
    let a = twice(id_impure); // OK!
    const b = twice(id_impure); // ERR!
    let c = twice(id_const); // OK!
    const d = twice(id_const); // OK!
    let e = twice(id_maybe_const); // OK!
    const f = twice(id_maybe_const); // OK!
}

Vou escrever um RFC propondo algo nesse sentido (e mais) em uma semana ou assim.

@Centril neste momento você está desenvolvendo um sistema de efeitos com polimorfismo de efeitos. Eu sei que essa sempre foi sua agenda secreta (?), apenas deixando você saber que está ficando descaradamente óbvio.^^

@RalfJung Eu já revelei o segredo em https://github.com/rust-lang/rfcs/pull/2237 ano passado, mas vou ter que reescrever ;)
Praticamente domínio público agora ^,-

@SoniEx2

sim, um ponteiro após o final de um elemento de matriz apontaria para o próximo elemento de matriz, provavelmente. E daí? não é realmente não determinístico? determinista em tempo de compilação é tudo o que importa de qualquer maneira. tanto quanto eu me importo, a avaliação do tempo de execução pode segfault para o esgotamento da pilha.

O problema está em situações como as seguintes (em C++):

int x[2];
int y; // let's assume y is put right after the end of x in the stack frame
if (&x[0] + 2 == &y) {
  // ...
}

Os compiladores C querem (e fazem!) otimizar essa comparação para false . Afinal, um ponteiro aponta para x e outro para y , então não é possível que eles sejam iguais.
Exceto, é claro, que os endereços são iguais na máquina porque um ponteiro aponta para o final de x , que é o mesmo endereço que (o início de) y ! Então, se você obscurecer o código o suficiente para que o compilador não veja mais de onde vêm os endereços, você pode dizer que a comparação é avaliada como true . O padrão C++, portanto, permite que ambos os resultados ocorram de forma não determinística, justificando tanto a otimização (que diz false ) quanto a compilação para assembly (que diz true ). O padrão C não permite isso, tornando os compiladores LLVM (e GCC) não conformes, pois ambos realizarão esses tipos de otimizações.

Eu escrevi um resumo das minhas ideias de segurança const, solidez const etc. que surgiram aqui neste tópico e/ou em discussões relacionadas no IRC: https://www.ralfj.de/blog/2018/07/19/ const.html

Esta questão aqui tornou-se um pouco difícil de desembaraçar porque muitas coisas foram discutidas. @oli-obk ajudou a criar um repositório para preocupações com const-eval, portanto, um bom lugar para discutir subquestões específicas é provavelmente o rastreador de problemas de https://github.com/rust-rfcs/const-eval.

@Centril sugeriu estabilizar uma versão mínima que seja compatível com futuras extensões:

  • sem argumentos genéricos com limites de traço
  • sem argumentos ou tipos de retorno do ponteiro fn ou do tipo dyn Trait

    • verificado recursivamente no tipo de argumento para que os campos de argumentos também não sejam desses tipos

  • nenhum código inseguro (porque não sabemos se há coisas lá que são problemáticas)

    • Pessoalmente, acho que está tudo bem, exceto union s que já estão atrás de um portão de recurso extra e derefs de ponteiro bruto (geralmente proibidos em qualquer constante agora). Qualquer outro código inseguro precisa passar por outros const fns inseguros ou intrínsecos const, que exigem sua própria estabilização de wrt de discussão.

(nit: minha sugestão também incluiu uma verificação recursiva para fn ponteiros ou dyn Trait no tipo de retorno de const fn s)

sem argumentos genéricos com limites de traço

Para esclarecer, algo assim seria aceito ou não?

struct Mutex<T> where T: Send { /* .. */ }

impl<T> Mutex<T> where T: Send {
    pub const fn new(val: T) -> Self { /* .. */ }
}

O limite não faz parte do próprio const fn .

Também para esclarecer: a proposta de estabilizar essas coisas e deixar o resto para trás do portão de recursos OU estabilizá-las e fazer o resto um erro?

@mark-im o resto ficaria atrás de um portão de recurso.

@oli-obk qual é o problema com o código inseguro? Nós permitimos unsafe em const X : Ty = ... , que tem os mesmos problemas. Acho que os corpos const fn devem ser verificados exatamente como os corpos const .

Acho que queremos permanecer conservadores em relação a operações "inconst" - operações que são seguras, mas não seguras para const (basicamente qualquer coisa em ponteiros brutos) - mas essas já são totalmente proibidas no contexto const, certo?

O limite não faz parte do próprio const fn.

Não, limites no bloco impl também não seriam permitidos sob essa proposta

qual é o problema com código inseguro?

Não vejo nenhum problema, conforme observado no meu comentário. Cada recurso/função inseguro const precisa passar por sua própria estabilização de qualquer maneira.

@RalfJung Eu acho que o problema é " @Centril está nervoso porque perdemos algo wrt. esses já são totalmente proibidos no contexto const". ;) Mas temos que estabilizar unsafe { .. } em const fn em algum momento então se você tem certeza que não há problemas e que pegamos todas as operações unconst então vamos fazer isso?

Além disso, se perdemos algo, já estamos meio ferrados, pois as pessoas podem usá-lo em const .

Eu ainda pretendo escrever um PR preenchendo as partes de segurança/promoção const do repositório const fn RFC ; essa seria a hora de verificar cuidadosamente se cobrimos tudo (e temos casos de teste).

Outra coisa que surgiu no Discord são as operações de FP: atualmente não podemos garantir que elas correspondam ao hardware real. O CTFE seguirá exatamente o IEEE, mas o LLVM/hardware talvez não.

Isso também se aplica a itens const , mas esses nunca serão executados em tempo de execução -- enquanto const fn pode ser. Portanto, parece prudente não estabilizar as operações de PF em const fn .

OTOH, já promovemos resultados das operações de PF? Portanto, já temos essa incompatibilidade de tempo de execução/tempo de compilação observável no stable. Valeria a pena uma corrida de cratera para ver se podemos desfazer isso?

Para referência futura, o seguinte artigo é relevante em relação a pontos flutuantes e determinismo:

@RalfJung

Valeria a pena uma corrida de cratera para ver se podemos desfazer isso?

Eu ficaria surpreso se pudéssemos fazer isso, mas pelo menos vale a pena tentar. :)

@RalfJung pode haver uma entrada interessante mais adiante neste tópico https://github.com/rust-lang/rust/issues/24111#issuecomment -386764565

Supondo que queremos manter a forma de palavra-chave const fn , acho que estabilizar algo agora , que é limitado o suficiente, é uma solução bastante decente (como não vi isso antes ?!)

Quando fazemos essas estabilizações fragmentadas, quero apenas registrar uma
solicitar uma lista clara das restrições e suas justificativas. Isso é
será frustrante quando os usuários fizerem uma mudança aparentemente inócua e
encontrar um erro de compilação, para que possamos pelo menos corrigir os erros. eu
reconhecer que o raciocínio está espalhado por muitas discussões, nem todas
Eu segui, mas acho que deveria haver uma tabela nos documentos (ou no
referência, ou o nomicon, pelo menos) listando cada operação não permitida, o
problema que poderia causar, e as perspectivas de estabilização (por exemplo, "nunca",
"se o RFC XYZ for implementado", "depois de definirmos esta parte da especificação").

Em segunda-feira, 20 de agosto de 2018 às 13h44 Eduard-Mihai Burtescu <
[email protected]> escreveu:

Supondo que queremos manter a palavra-chave const fn, acho que estabilizando
algo agora , que é limitado o suficiente, é uma solução bastante decente (como
não vi antes?!)


Você está recebendo isso porque foi mencionado.
Responda a este e-mail diretamente, visualize-o no GitHub
https://github.com/rust-lang/rust/issues/24111#issuecomment-414403036 ,
ou silenciar o thread
https://github.com/notifications/unsubscribe-auth/AAC3n80yVxIa3agsJP4wXZFkgyHVmAsjks5uSvWXgaJpZM4D66IA
.

@est31 Como o @rkruppe já escreveu, esses fusíveis seriam ilegais sem -ffast-math - e acho que o LLVM lida com isso corretamente.

Pelo que me lembro, a aritmética básica nem é tão problemática (exceto em x86 de 32 bits porque x87 ...), mas as funções transcendentais são. E esses não são const fn , certo? Então minha esperança seria que no final possamos ter paridade entre const itens, promoção e const fn nesse quesito também.

@RalfJung Ainda não estou convencido de que, por exemplo, derramar e depois carregá-lo de volta nos registros da FPU entre algumas operações fornece os mesmos resultados que a mesma computação sem derramar.

Pelo que me lembro, a aritmética básica nem é tão problemática (exceto em x86 de 32 bits porque x87 ...), mas as funções transcendentais são.

Como as funções transcendentais significariam um problema?

IIRC, enquanto algumas funções transcendentais são suportadas por processadores x86 comuns, essas funções são lentas e evitadas, e incluídas apenas para integridade e compatibilidade com implementações existentes. Assim, em quase todos os lugares, as funções transcendentais são expressas em termos de combinações de funções aritméticas básicas. Isso significa que não há diferença em sua transparência referencial para as funções aritméticas. Se as funções básicas são "seguras", então qualquer coisa construída sobre elas é, incluindo funções transcendentais. A única fonte de "intransparência referencial" aqui pode ser diferentes aproximações (implementações) dessas funções transcendentais em termos dessas funções aritméticas básicas. Essa é a origem do problema?

@est31 Enquanto a maioria das funções transcendentais são, em última análise, apenas código de biblioteca composto de operações de número inteiro primitivo e float, essas implementações não são padronizadas e, na prática, um programa Rust pode interagir com talvez três implementações diferentes ao longo de sua vida útil, algumas das quais também variam de acordo com o host ou destino plataforma:

  • Existe a implementação de tempo de execução que o programa usa no destino (por exemplo, a libm da plataforma de destino ou instruções de hardware em algumas circunstâncias)
  • Há a pasta constante que o LLVM usa (aparentemente, esta é apenas a plataforma host C libm)
  • Há o que o MIRI faria com essas operações (por exemplo, algo em rustc_apfloat ou interpretar uma implementação de Rust como https://github.com/japaric/libm/)

Se algum deles discordar um do outro, você poderá obter resultados diferentes dependendo de quando uma expressão for avaliada.

@rfcbot cancelar

É improvável que estabilizemos o monte completo em um futuro próximo.
Em vez disso, gostaria de desenvolver um consenso para um subconjunto mais mínimo (conforme descrito em https://github.com/rust-lang/rust/issues/24111#issuecomment-414310119) que esperamos poder estabilizar no curto prazo .
Este subconjunto é rastreado em #53555. Mais descrição está disponível lá.

Proposta @Centril cancelada.

@rkruppe existe uma razão para diminuir as funções transcendentais para intrínsecos llvm? Não podemos simplesmente evitar todo o problema reduzindo-os a implementações bem conhecidas e somente ferrugem que controlamos e que são as mesmas em todas as plataformas?

existe uma razão para rebaixar as funções transcendentais a intrínsecas?

Além da simplicidade de não implementar uma biblioteca multiplataforma completa, os intrínsecos têm vantagens no otimizador e codegen do LLVM que uma função de biblioteca comum não terá. Obviamente, a dobragem constante (e coisas relacionadas, como análise de intervalo de valores) é um problema neste contexto, mas é bastante útil de outra forma. Há também identidades algébricas (aplicadas pela passagem SimplifyLibCalls). Finalmente, algumas funções (principalmente sqrt e sua recíproca, que não são transcendentais, mas o que for) têm suporte especial para geração de código para, por exemplo, gerar sqrtss em x86 com SSE.

Não podemos simplesmente evitar todo o problema reduzindo-os a implementações bem conhecidas e somente ferrugem que controlamos e que são as mesmas em todas as plataformas?

Mesmo ignorando todos os itens acima, essa não é uma ótima opção IMO. Se a plataforma de destino tiver um C libm, deve ser possível usá-lo, seja porque é mais otimizado ou para evitar inchaço.

os intrínsecos têm vantagens no otimizador e codegen do LLVM que uma função de biblioteca comum não terá.

Eu pensei que essas otimizações só estão sendo ativadas se a matemática rápida estiver ativada?

Se a plataforma de destino tiver um C libm, deve ser possível usá-lo, seja porque é mais otimizado ou para evitar inchaço.

Certo. Sempre deve haver uma opção para trocar essa propriedade bastante acadêmica - transparência referencial - em favor de coisas que importam mais como velocidade aprimorada do binário compilado ou binários menores. Por exemplo, usando a plataforma libm ou ativando o modo de matemática rápida. Ou você sugere que devemos proibir o modo de matemática rápida em toda a eternidade?

IMO, não devemos proibir f32::sin() em contextos const, pelo menos não se estivermos permitindo + , - etc. Tal proibição forçará as pessoas a criar e usar caixas que fornecem implementações compatíveis com const.

Eu pensei que essas otimizações só estão sendo ativadas se a matemática rápida estiver ativada?

A avaliação constante dessas funções e a emissão de sqrtss são fáceis de justificar sem -ffast-math, pois podem ser feitas corretamente arredondadas.

Ou você sugere que devemos proibir o modo de matemática rápida em toda a eternidade?

Não estou sugerindo nada, nem tenho opinião (atm) sobre se tal propriedade deve ser garantida. Estou simplesmente relatando restrições.

Não foi possível encontrar um problema aberto para o ICE causado por Vec no contexto const fn.

https://play.rust-lang.org/?version=nightly&mode=debug&edition=2015&gist=508238a9f06fd85720307bf6cc227586

Devo abrir um novo tópico para isso?

@Voultapher sim, parece um novo ICE.

Pronto, abri o nº 55063.

Se o compilador é capaz de verificar se uma função pode ser chamada em um constexpr em tempo de compilação quando um usuário a anota com const fn , por que não executar a verificação automaticamente em todas as funções? (semelhante a auto-traços)? Não consigo pensar em nenhum efeito adverso real, e o benefício claro é que não precisamos depender do julgamento humano propenso a erros.

A maior desvantagem é que se torna um detalhe público, então uma implementação
mudança em uma função que não deveria ser const agora está falhando.

Também não precisamos confiar no julgamento humano. Podemos ter um lint clippy que nos diz quando uma função não anotada pode ser const fn : https://github.com/rust-lang/rust-clippy/issues/2440

Isso é semelhante a como não inferimos a mutabilidade de variáveis ​​locais, mas, em vez disso, o compilador nos diz onde adicionar ou remover mut .

@remexre const fn atua como uma especificação de interface. Não estou muito familiarizado com os pequenos detalhes desse recurso (e talvez o que segue aqui já tenha sido pensado), mas dois casos em que posso pensar no compilador dizendo quando uma função é anotada incorretamente como const é falhar na compilação se tal função receber um &mut como parâmetro ou se chamar outras funções que não sejam const . Portanto, se você alterar a implementação de um const fn e quebrar essas restrições, o compilador o interromperá. Em seguida, você pode optar por implementar os bits não const em (uma) função(ões) separada(s) ou interromper a API se essa for uma alteração pretendida.

Há outro ponto médio que não vi discutido e é a possibilidade de introduzir um oposto desse marcador e algum tipo de "inferência de pureza de função" quando não estiver explicitamente definido. Em seguida, os documentos mostrariam o marcador real, mas com algum tipo de aviso sobre não garantir a estabilidade desse marcador se for um const . O problema é que isso pode encorajar ser preguiçoso e fazer isso quase todas as vezes, o que não é seu propósito.

um const fn deve ser capaz de produzir saída? por que &mut não é permitido?

@aledomu Meu comentário foi direcionado a @AGaussman; Estou falando sobre o caso em que um autor de biblioteca expõe uma função que não é "destinada a ser" const (na medida em que a const-ness não se destina a fazer parte da API); se const fosse inferida, seria uma mudança de ruptura tornar a referida função não const.

@SoniEx2 const fn é uma função que pode ser avaliada em tempo de compilação, o que acontece apenas com qualquer função pura.

@remexre Se não for para ser uma parte estável da API, apenas não a marque.

Para o bit de inferência que comentei, é por isso que mencionei a necessidade de algum aviso nos documentos da caixa.

qual é a diferença? absolutamente nenhum!

const fn add_1(x: &mut i32) { x += 1; }
let mut x = 0;
add_1(&mut x);
assert_eq!(x, 1);
x = 0;
add_1(&mut x);
assert_eq!(x, 1);

const fn added_1(x: i32) -> i32 { x + 1 }
let mut x = 0;
x = added_1(x);
assert_eq!(x, 1);
x = 0;
x = added_1(x);
assert_eq!(x, 1);

Eu arquivei problemas direcionados para:

Os seguintes problemas direcionados já existem:

Se houver outras áreas, ainda não rastreadas por outras questões, que precisam ser discutidas wrt. const eval e const fn , sugiro que as pessoas façam novos problemas (e me cc + @oli-obk neles).

Isso conclui a utilidade deste número, que está encerrado.

Não tenho todos os detalhes em mente, mas não há muito mais que seja suportado pelo miri, mas ainda não ativado em min_const_fn ? Por exemplo, ponteiros brutos.

@SimonSapin Sim, boa captura. Existem mais alguns problemas existentes para isso. Atualizei o comentário + a descrição do problema. Se houver algo não coberto que você encontre, faça novos problemas, por favor.

Eu acho que não é apropriado fechar um problema de rastreamento de meta quando não está claro que o que ele cobre é exaustivamente coberto por problemas mais específicos.

Quando eu removo #![feature(const_fn)] no Servo, as mensagens de erro são:

  • trait bounds other than `Sized` on const fn parameters are unstable
  • function pointers in const fn are unstable

(Esses const fn s são todos construtores triviais para tipos com campos privados. A mensagem anterior está no construtor de struct Guard<T: Clone + Copy> , embora Clone não seja usado no construtor. último é para inicializar Option<fn()> (simplificado) para None ou Some(name_of_a_function_item) .)

No entanto, nem as características nem os tipos de ponteiro de função são mencionados na descrição deste problema.

Não quero dizer que devemos ter apenas mais duas questões específicas para o acima. Quero dizer, devemos reabrir este até que, de alguma forma, garantamos que tudo por trás do portão de recurso const_fn (que ainda aponta aqui nas mensagens de erro) tenha um problema de rastreamento. Ou até que const_fn esteja totalmente estabilizado.

@SimonSapin

Eu acho que não é apropriado fechar um problema de rastreamento de meta quando não está claro que o que ele cobre é exaustivamente coberto por problemas mais específicos.

Este problema tem o sabor de https://github.com/rust-lang/rust/issues/34511 , que é uma das maiores bagunças que existem no que diz respeito aos problemas de rastreamento. Esse problema também é gratuito há algum tempo, então não funciona como um meta-problema no momento. Para tais free-for-alls, use http://internals.rust-lang.org/ em vez disso.

No entanto, nem as características nem os tipos de ponteiro de função são mencionados na descrição deste problema.

Não quero dizer que devemos ter apenas mais duas questões específicas para o acima.

É exatamente o que eu acho que deveria ser feito. De uma perspectiva de triagem T-Lang, é favorável ter problemas direcionados e acionáveis .

Quero dizer, devemos reabrir este até que, de alguma forma, garantamos que tudo por trás do portão de recurso const_fn (que ainda aponta aqui em mensagens de erro) tenha um problema de rastreamento. Ou até que const_fn esteja totalmente estabilizado.

Não está claro para mim o que const_fn o feature gate constitui ou que tudo será estabilizado em algum momento. Tudo além de limites e ponteiros de função do RFC original tem problemas em aberto e mais alguns.

Não está claro para mim o que const_fn o portão de recurso constitui

É exatamente por isso que não devemos fechá-lo até descobrirmos isso, IMO.

Tudo

Mas é tudo mesmo?

Alguém sabe o que aconteceu com o recurso const_string_new ? Existe algum problema de rastreamento para ele? O livro instável apenas links aqui.

@phansch Isso porque todos os pontos rustc_const_unstable aqui. (cc @oli-obk podemos consertar isso?)

A questão deve estar aberta então. É apenas um insulto como usuário ser apontado
a um assunto encerrado.

Na quarta-feira, 9 de janeiro de 2019, 04:05 Mazdak Farrokhzad < [email protected]
escrevi:

@phansch https://github.com/phansch Isso porque todos
ponto rustc_const_unstable aqui.


Você está recebendo isso porque foi mencionado.
Responda a este e-mail diretamente, visualize-o no GitHub
https://github.com/rust-lang/rust/issues/24111#issuecomment-452622097 ,
ou silenciar o thread
https://github.com/notifications/unsubscribe-auth/AAC3n7JhzsZZpizmWlp0Nww5bcfIqH2Vks5vBbC8gaJpZM4D66IA
.

@durka : Sempre haverá uma janela possível em que algo é fechado todas as noites e a resolução ainda não chegou ao estável. Como isso é um insulto?

Tenho resistido a comentar aqui, e talvez devêssemos mover essa conversa para um tópico interno (já existe algum?) mas...

A decisão de fechar isso não faz sentido para mim. É um problema de rastreamento, porque aparece em mensagens de erro do compilador, e não está sozinho, veja este post para mais alguns exemplos: https://internals.rust-lang.org/t/psa-tracking-for-gated -language-features/2887. Fechar esta questão para mim implica estabilidade, o que obviamente ainda não é o caso.

Sinceramente, não consigo ver um argumento para fechar isso ... Estou feliz que agora existe um problema mais direcionado, para que a implementação possa avançar, com nova discussão e foco, mas não vejo uma maneira clara de associar o compilador mensagens com eles.

Novamente, se isso precisar (ou já tiver) um tópico sobre internos, talvez vamos mover essa conversa para lá?

EDIT: Ou o problema é apenas que o livro está desatualizado? Tentando o exemplo do RFC (está faltando alguns #[derive(...)] s) parece funcionar sem erros no Rust rustc 1.31.1. Ainda há mensagem de erro do compilador apontando aqui? Seria bom ter um lugar para vincular erros como:

error: only int, `bool` and `char` operations are stable in const fn

Se quisermos tê-los vinculados às questões específicas, isso possivelmente seria uma melhoria.

Ok, então aqui deve haver alguma evidência forte para este problema permanecer em aberto. Até onde posso dizer isso:

https://github.com/rust-lang/rust/blob/6ecad338381cc3b8d56e2df22e5971a598eddd6c/src/libsyntax/feature_gate.rs#L194

é o único recurso active que aponta para um problema encerrado.

Em um mundo ideal, acredito que esse tipo de discussão realmente deveria ser automatizado, pois, como descobrimos, as pessoas têm opiniões e ideias variadas sobre como as coisas devem funcionar. Mas isso não é realmente uma conversa para este tópico...

Se quisermos tê-los vinculados às questões específicas, isso possivelmente seria uma melhoria.

Sim, esta é a solução correta e o que o @Centril já sugeriu .

O comentário inicial também foi editado para redirecionar as pessoas para as questões específicas que chegam aqui na "janela" que @ErichDonGubler menciona .

https://github.com/rust-lang/rust/issues/57563 agora foi aberto para rastrear os recursos const instáveis ​​restantes.

Alguém poderia editar o corpo da edição aqui para vincular com destaque ao # 57563 então?

@glaebhoerl feito :)

Olá, cheguei aqui porque recebi error[E0658]: const fn is unstable (see issue #24111) ao compilar ncurses-rs. O que devo fazer? Atualizar ferrugem? eu tenho

$ cargo version
cargo 1.27.0
$ rustc --version
rustc 1.27.2

EDIT: fiz brew uninstall rust e segui as instruções de instalação do rustup , agora rustc --version é rustc 1.33.0 (2aa4c46cf 2019-02-28) e esse erro desapareceu.

Sim, para poder usar const fn no stable, você precisará atualizar seu compilador.

Esta página foi útil?
0 / 5 - 0 avaliações