Skip to main content

Web Assembly | Rust

  • Web Assembly | Rust

Resumo Detalhado do WebAssembly

alt text

WebAssembly (Wasm) é um formato de instrução binário (linguagem de baixo nível) portátil que oferece um tempo de execução eficiente e seguro para aplicações web. Foi projetado para complementar o JavaScript, permitindo que linguagens como C, C++ e Rust sejam compiladas para um formato que pode ser executado em navegadores e outros ambientes.

Principais Benefícios do WebAssembly:

  1. Desempenho: WebAssembly é executado quase na velocidade nativa, permitindo desempenho superior comparado ao JavaScript.
  2. Portabilidade: Código compilado em WebAssembly pode ser executado em qualquer ambiente que suporte o formato, incluindo navegadores e servidores.
  3. Segurança: WebAssembly opera dentro do sandbox do navegador, oferecendo segurança comparável ao JavaScript.
  4. Interoperabilidade: WebAssembly pode interagir diretamente com JavaScript, facilitando a integração com código existente.

Comandos Cargo para Gerar Pacote WebAssembly

Para compilar um projeto Rust em WebAssembly, você precisa seguir algumas etapas. Aqui estão os comandos básicos que você deve usar:

  1. Instalar os Alvos WebAssembly:

    rustup target add wasm32-unknown-unknown
  2. Criar um Novo Projeto Rust:

    cargo new --lib my_wasm_project
    cd my_wasm_project
  3. Adicionar as Dependências Necessárias no Cargo.toml:

    [dependencies]
    wasm-bindgen = "0.2"

    [lib]
    crate-type = ["cdylib"]
  4. Compilar o Projeto para WebAssembly:

    cargo build --target wasm32-unknown-unknown

Vinculação em uma Página HTML com JavaScript

Para integrar o WebAssembly em uma página HTML, você precisará usar o wasm-bindgen para gerar o código de ligação (bindings) entre Rust e JavaScript. Aqui estão os passos:

  1. Adicionar o wasm-bindgen ao seu projeto:

    cargo install wasm-bindgen-cli
  2. Compilar o Projeto com wasm-bindgen:

    cargo build --target wasm32-unknown-unknown --release
    wasm-bindgen --target web --out-dir . ./target/wasm32-unknown-unknown/release/my_wasm_project.wasm
  3. Criar uma Página HTML e Integrar o WebAssembly:

    • HTML (index.html):

      <!DOCTYPE html>
      <html>
      <head>
      <meta charset="UTF-8">
      <title>WebAssembly with Rust</title>
      </head>
      <body>
      <h1>WebAssembly with Rust</h1>
      <div id="output"></div>
      <script type="module">
      import init, { greet } from './my_wasm_project.js';

      async function run() {
      await init();
      greet();
      }

      run();
      </script>
      </body>
      </html>
    • Rust (src/lib.rs):

      use wasm_bindgen::prelude::*;

      #[wasm_bindgen]
      pub fn greet() {
      alert("Hello, WebAssembly with Rust!");
      }

Conclusão

Com esses passos, você deve ser capaz de criar, compilar e vincular um módulo WebAssembly com Rust a uma página HTML. Essa configuração básica permite que você comece a explorar o potencial do WebAssembly para construir aplicações web de alto desempenho. Para projetos mais complexos, considere o uso de frameworks e bibliotecas que facilitam o desenvolvimento com WebAssembly e Rust, como yew para interfaces de usuário ou wasm-pack para empacotamento e publicação.

Se precisar de mais detalhes ou tiver dúvidas específicas sobre alguma etapa, sinta-se à vontade para perguntar!

crate-type

O crate_type em Rust é utilizado para especificar o tipo de crate (pacote) que você está criando. Um crate pode ser de diferentes tipos, como um binário executável, uma biblioteca, ou outros tipos especializados. Esse atributo é particularmente útil quando você precisa que seu crate seja compilado como um tipo específico.

Tipos de Crate

Os tipos de crate mais comuns são:

  • bin: Um executável. Esse tipo de crate gera um arquivo binário que pode ser executado.
  • lib: Uma biblioteca. Esse tipo de crate gera um arquivo que pode ser vinculado a outros projetos.
  • cdylib: Uma biblioteca dinâmica compatível com outras linguagens.
  • rlib: Uma biblioteca compilada específica para ser usada com o Rust.
  • staticlib: Uma biblioteca estática que pode ser usada por outras linguagens.
  • proc-macro: Um crate que define macros de procedimento, que são macros que geram código Rust.

Usando crate_type no Código

Você pode especificar o tipo de crate diretamente no código Rust usando o atributo #[crate_type]. Esse atributo deve ser colocado no topo do seu arquivo principal, como main.rs ou lib.rs. Por exemplo:

// lib.rs
#![crate_type = "lib"]

// código da biblioteca

Este exemplo indica que o crate deve ser compilado como uma biblioteca.

// main.rs
#![crate_type = "bin"]

fn main() {
println!("Hello, world!");
}

Este exemplo indica que o crate deve ser compilado como um executável.

Usando crate_type no Parâmetro do Comando

Você também pode especificar o tipo de crate ao compilar seu código usando a ferramenta de linha de comando rustc diretamente. Por exemplo:

rustc --crate-type=lib src/lib.rs

Neste exemplo, o comando rustc está sendo usado para compilar o arquivo lib.rs como uma biblioteca. Da mesma forma, você pode compilar como binário:

rustc --crate-type=bin src/main.rs

No Cargo (Ferramenta de Gerenciamento de Crates)

Se estiver usando o Cargo, que é a ferramenta de gerenciamento de pacotes e build system do Rust, o tipo do crate é normalmente inferido automaticamente com base na presença de um arquivo main.rs (para executáveis) ou lib.rs (para bibliotecas). No entanto, você ainda pode especificá-lo manualmente no arquivo Cargo.toml se necessário.

Por exemplo, para um crate que deve ser compilado como uma biblioteca dinâmica:

[lib]
crate-type = ["cdylib"]

Resumo

  • crate_type define o tipo de pacote (crate) no Rust.
  • No código, você o define usando o atributo #![crate_type = "tipo"].
  • Na linha de comando com rustc, use --crate-type=tipo.
  • No Cargo, você pode especificá-lo em Cargo.toml no bloco [lib].

Isso permite controlar como seu código será compilado e distribuído, seja como um executável ou como uma biblioteca para uso em outros projetos.

ABI, ou Application Binary Interface (Interface Binária de Aplicação), é um conjunto de convenções que governam como funções são chamadas, como os parâmetros são passados, como o retorno das funções é tratado, como a memória é organizada, e como o código é organizado e executado em um sistema operacional e arquitetura de hardware específico. Diferente da API (Application Programming Interface), que define como os componentes de software interagem no nível do código-fonte, a ABI define essa interação no nível binário.

ABI em Rust

Rust é uma linguagem de programação que oferece segurança de memória e concorrência sem concessões. Quando se trata de ABI, Rust normalmente não especifica um ABI estável para a maioria de suas funções, exceto em casos onde é necessário interoperar com outras linguagens, como o C. Isso significa que o código Rust não garante compatibilidade binária entre diferentes versões do compilador ou mesmo entre diferentes compiladores.

Motivos para a Ausência de um ABI Estável em Rust

  1. Evolução da Linguagem: O compilador Rust continua evoluindo rapidamente, e fornecer um ABI estável poderia restringir essa evolução.
  2. Otimizações: Rust realiza muitas otimizações em nível de compilação, e fixar um ABI poderia limitar a capacidade do compilador de aplicar essas otimizações.

Interoperabilidade com Outras Linguagens

Embora Rust não tenha um ABI próprio estável, ele pode interoperar com outras linguagens como C, que tem um ABI amplamente suportado e bem definido. Essa interoperabilidade é crucial para a integração de Rust em projetos que envolvem múltiplas linguagens ou onde Rust precisa chamar funções escritas em C e vice-versa.

Para definir que uma função Rust usa o ABI de C, você pode usar o atributo extern "C":

extern "C" fn add(a: i32, b: i32) -> i32 {
a + b
}

Neste exemplo, a função add tem um ABI de C, o que significa que pode ser chamada por código C, e a convenção de chamada usada será compatível com o que o C espera.

Usos Comuns de ABI em Rust

  1. Chamada de Funções Externas: Você pode chamar funções de bibliotecas C diretamente em Rust. Isso é feito utilizando o atributo extern junto com o ABI "C".

    extern "C" {
    fn printf(format: *const i8, ...) -> i32;
    }

    Este exemplo declara a função printf do C, permitindo que você a chame dentro do código Rust.

  2. Exportação de Funções para Outras Linguagens: Se você deseja que seu código Rust seja utilizado em um programa C, você pode exportar funções utilizando #[no_mangle] junto com extern "C" para impedir que o compilador mude o nome das funções (name mangling).

    #[no_mangle]
    pub extern "C" fn my_function(x: i32) -> i32 {
    x + 1
    }
  3. Forthcoming ABI (Future Compatibility): Algumas novas convenções de ABI específicas para Rust podem ser introduzidas no futuro, mas elas ainda estão em estágios experimentais.

Considerações Importantes

  • ABI de Rust Não é Estável: Como mencionado, Rust não tem um ABI estável. O que isso significa é que funções Rust não devem ser diretamente chamadas de código compilado em diferentes versões do compilador, pois a convenção binária de chamada pode mudar.

  • extern "Rust": Existe um ABI "Rust" que é o padrão para funções Rust, mas ele não é estável entre versões do compilador e é usado apenas dentro do ecossistema Rust.

Resumo

O ABI em Rust é fundamental quando se lida com interoperabilidade entre Rust e outras linguagens, especialmente C. Rust usa a convenção de chamada definida pelo ABI para garantir que funções possam ser chamadas entre diferentes linguagens, mas por design, Rust não define um ABI estável para si mesmo, a fim de permitir uma evolução e otimização contínuas do compilador.

Se você está desenvolvendo bibliotecas que serão usadas por outras linguagens, entender e utilizar corretamente o ABI é crucial para garantir a interoperabilidade e evitar problemas de compatibilidade binária.

O que é Name Mangling?

O atributo #[no_mangle] em Rust é utilizado para instruir o compilador a não realizar o name mangling (ou embaralhamento de nomes) em uma função ou variável.

Name mangling é o processo pelo qual o compilador transforma o nome das funções e variáveis para incluir informações adicionais, como o namespace, número e tipos dos parâmetros, entre outros. Esse processo ajuda a evitar conflitos de nomes em linguagens que suportam sobrecarga de funções ou espaços de nomes, como C++ e Rust. O nome real de uma função pode ser alterado para algo que inclua informações específicas da função, como tipos de argumentos ou mesmo a localização dentro de um módulo.

Por exemplo, uma função Rust chamada foo pode ser transformada em algo como _ZN3fooE (esse é apenas um exemplo simplificado, na prática o resultado pode ser bem mais complexo). Esse nome embaralhado é o que o compilador usa internamente e é o que aparece nos binários.

Por que usar #[no_mangle]?

O atributo #[no_mangle] é utilizado quando você deseja que o nome da função ou variável apareça exatamente como foi definido no código, sem o processo de name mangling. Isso é especialmente útil quando você está escrevendo código Rust que precisa ser chamado de outras linguagens, como C ou C++, onde você deseja expor funções com nomes específicos.

Exemplo de Uso

Aqui está um exemplo de como #[no_mangle] é utilizado em Rust:

#[no_mangle]
pub extern "C" fn my_function(x: i32) -> i32 {
x + 1
}

Explicação do Exemplo

  • #[no_mangle]: Garante que o nome da função exportada seja exatamente my_function, sem qualquer modificação pelo compilador.
  • pub extern "C": Faz com que a função use a convenção de chamada do C, permitindo que ela seja chamada por código C.
  • Função my_function: É uma função pública que pode ser chamada de código C sem que o nome seja modificado.

Quando usar #[no_mangle]?

  • Interoperabilidade com C/C++: Quando você está criando uma biblioteca em Rust que será usada por código C ou C++, você deve usar #[no_mangle] para garantir que o código C/C++ possa encontrar as funções pelo nome correto.

  • Interação com APIs em outras linguagens: Se você precisar expor funções para serem chamadas de outras linguagens ou sistemas, #[no_mangle] ajuda a manter os nomes das funções previsíveis.

Considerações

  • Risco de Colisões de Nomes: Sem name mangling, você corre o risco de colisões de nomes se houver múltiplas funções com o mesmo nome em diferentes módulos ou bibliotecas. Isso pode levar a comportamentos inesperados ou erros de linkagem.

  • Uso Limitado: #[no_mangle] deve ser usado apenas quando necessário, pois o name mangling é uma ferramenta útil para evitar conflitos de nomes dentro de programas complexos.

Em resumo, #[no_mangle] é uma ferramenta importante para garantir compatibilidade de nomes quando Rust interage com outras linguagens ou sistemas, mas deve ser usada com cuidado para evitar problemas de colisão de nomes.

No Rust, os conceitos de link e link_name são utilizados em contextos de interoperabilidade com código escrito em outras linguagens, como C. Esses conceitos são parte do sistema FFI (Foreign Function Interface) do Rust, que permite que o Rust chame funções e utilize variáveis de bibliotecas externas.

O atributo link é utilizado para especificar uma biblioteca externa que você deseja ligar ao seu código Rust. Esse atributo é geralmente usado em blocos extern para indicar que funções ou variáveis declaradas dentro desse bloco são definidas em uma biblioteca externa, e que o linker deve procurar essas definições nessa biblioteca.

Por exemplo:

#[link(name = "mylib")]
extern "C" {
fn my_function();
}

Neste exemplo:

  • #[link(name = "mylib")]: O atributo link informa ao compilador que a função my_function está localizada em uma biblioteca chamada mylib (normalmente, libmylib.a ou mylib.so no Linux, ou mylib.dll no Windows).

O atributo link_name é usado para especificar o nome real de uma função ou variável na biblioteca externa, caso esse nome seja diferente do que você deseja usar no código Rust. Isso é útil quando o nome no código Rust deve ser diferente do nome no código da biblioteca (por exemplo, devido a convenções de nomeação ou para evitar conflitos de nomes).

Por exemplo:

#[link(name = "mylib")]
extern "C" {
#[link_name = "extern_function_name"]
fn my_function();
}

Neste exemplo:

  • #[link_name = "extern_function_name"]: Especifica que a função my_function no Rust na verdade corresponde a uma função chamada extern_function_name na biblioteca externa mylib.

Como Usar em um Bloco Externo

Blocos extern em Rust são usados para declarar funções ou variáveis que são definidas fora do programa Rust, em outras bibliotecas (normalmente C). Dentro desses blocos, você pode usar os atributos link e link_name para associar funções ou variáveis com seus equivalentes na biblioteca externa.

Aqui está um exemplo de uso completo:

#[link(name = "mylib")]
extern "C" {
#[link_name = "extern_function_name"]
fn my_function();
}

fn main() {
unsafe {
my_function();
}
}

Explicação do comando rustc

  • O bloco extern "C" indica que estamos declarando funções que seguem a convenção de chamada da linguagem C.
  • #[link(name = "mylib")] especifica que essas funções estão em uma biblioteca chamada mylib.
  • #[link_name = "extern_function_name"] mapeia a função my_function para a função extern_function_name na biblioteca mylib.
  • Dentro da função main, a função my_function é chamada usando unsafe, já que chamar código externo (FFI) é considerado uma operação insegura no Rust, pois o compilador não pode garantir a segurança de memória do código externo.

Esse é um resumo básico de como usar link e link_name em Rust. Se você tiver mais perguntas ou precisar de mais detalhes, fique à vontade para perguntar!

Em Rust, o comando rustc é utilizado para compilar o código, e a flag --target define o ambiente de destino para o qual o código será compilado. Vamos detalhar o que cada uma dessas opções (--target e --crate-type) significa e como elas diferem.

--target

A opção --target especifica a plataforma de destino para a qual o código Rust será compilado. Isso pode incluir o tipo de arquitetura, sistema operacional e até o tipo de binário. O valor padrão para --target é a plataforma nativa do sistema onde a compilação está sendo executada.

No seu exemplo, wasm32-unknown-unknown é o valor definido para --target, que é uma especificação para compilar o código para WebAssembly (WASM), com as seguintes características:

  • wasm32: Refere-se à arquitetura de 32 bits do WebAssembly.
  • unknown: Indica que o sistema operacional e o ambiente são desconhecidos, o que significa que o código é agnóstico a eles, apropriado para um ambiente como o WebAssembly, que não tem um sistema operacional nativo.
  • unknown: O segundo unknown indica que não há um ABI (Application Binary Interface) específico definido.

Outros Exemplos de Targets:

Alguns outros exemplos de targets que podem ser utilizados com rustc incluem:

  • x86_64-unknown-linux-gnu: Para compilar para Linux em uma arquitetura x86_64.
  • x86_64-pc-windows-msvc: Para compilar para Windows com o compilador MSVC.
  • aarch64-apple-darwin: Para compilar para macOS em dispositivos com arquitetura ARM (Apple Silicon).

--crate-type

A opção --crate-type define o tipo de artefato que será gerado pela compilação. Em Rust, crates são as unidades de compilação. Existem diferentes tipos de crates que você pode especificar:

  • bin: Gera um executável. Este é o padrão se o crate tiver uma função main.
  • lib: Gera uma biblioteca estática (arquivo .rlib).
  • rlib: Gera uma "Rust library", uma biblioteca no formato nativo do Rust.
  • dylib: Gera uma biblioteca dinâmica que pode ser usada por outros programas (um .so em Linux, .dll em Windows, .dylib em macOS).
  • cdylib: Gera uma biblioteca dinâmica com uma interface de C. Diferente de dylib, cdylib é usada quando se deseja que a biblioteca seja chamada a partir de código C (ou outra linguagem) sem dependências específicas do Rust.
  • staticlib: Gera uma biblioteca estática que pode ser linkada em código C (ou outras linguagens).

Diferença entre --target e --crate-type

  • --target: Define a plataforma ou ambiente de destino. Ele diz ao compilador para qual arquitetura ou sistema operacional o código deve ser compilado.
  • --crate-type: Define o tipo de saída ou artefato que será gerado. Ele especifica que tipo de binário ou biblioteca deve ser produzido pelo compilador.

Essas opções podem ser combinadas para, por exemplo, compilar uma biblioteca dinâmica (cdylib) para WebAssembly (wasm32-unknown-unknown), como no comando que você mencionou.

Essa combinação (--target wasm32-unknown-unknown com --crate-type cdylib) é típica quando você quer gerar um WebAssembly que será usado como uma biblioteca de chamada externa (por exemplo, em um projeto JavaScript que chama código WebAssembly).