Na maioria dos idiomas, você terá que reescrever toda a biblioteca do zero, e os primeiros resultados aparecerão no final do projeto. Essas portas tendem a ser muito caras e sujeitas a erros, e muitas vezes falham no meio. Joel Spolsky explica isso muito melhor do que eu em um artigo sobre por que o retrabalho completo é uma má ideia .
No entanto, Rust tem uma reviravolta matadora quando se trata desse tipo de coisa. Ele pode invocar código C sem a sobrecarga (ou seja, o ambiente P / Invoke em C #) e expõe funções que podem ser usadas em C como qualquer outra função em C. Isso abre a porta para uma abordagem alternativa:
Portar bibliotecas para Rust, uma função por vez.
Observação
O código neste artigo está disponível no GitHub . Sinta-se à vontade para pedir emprestado código ou inspiração.
Se você achou o artigo útil ou notou um erro, informe-me no rastreador de bug do blog !
Começando
Antes de fazer qualquer coisa, você precisa criar um novo projeto. Eu tenho um modelo que instala CI e licenças para gerar carga .
$ cargo generate --git https://github.com/Michael-F-Bryan/github-template --name tinyvm-rs
$ cd tinyvm-rs && tree
tree -I 'vendor|target'
.
├── Cargo.toml
├── LICENSE_APACHE.md
├── LICENSE_MIT.md
├── README.md
├── .travis.yml
└── src
└── lib.rs
1 directory, 6 files
Nosso primeiro desafio real é construir a biblioteca que queremos portar e descobrir um pouco.
Neste caso, estamos portando jakogut / tinyvm ,
TinyVM é uma máquina virtual pequena, rápida e leve escrita em ANSI C. puro.
Para tornar mais fácil referenciá-lo no futuro, vamos adicionar o repositório como um submódulo ao nosso projeto.
$ git submodule add https://github.com/jakogut/tinyvm vendor/tinyvm
Agora vamos dar uma olhada no código-fonte. Para começar,
README.md
as instruções de montagem.
TinyVM é a menor máquina virtual. Baixo uso de memória, pequena quantidade de código e pouco código binário.(ênfase adicionada)
A construção é feita em sistemas semelhantes ao UNIX com make e GCC.
Sem dependências externas, mantenha a biblioteca padrão C. As
compilações são feitas com make ou make rebuild.
Para construir uma versão de depuração, adicione DEBUG = yes após make. Para construir um binário com perfil habilitado, adicione PROFILE = yes após make.
Você pode entrar em contato comigo em joseph.kogut (at) gmail.com
Ok, vamos dar uma olhada no diretório
tinyvm
e ver se a construção simplesmente funciona .
$ cd vendor/tinyvm
$ make
clang -Wall -pipe -Iinclude/ -std=gnu11 -Werror -pedantic -pedantic-errors -O3 -c libtvm/tvm_program.c -o libtvm/tvm_program.o
clang -Wall -pipe -Iinclude/ -std=gnu11 -Werror -pedantic -pedantic-errors -O3 -c libtvm/tvm_lexer.c -o libtvm/tvm_lexer.o
clang -Wall -pipe -Iinclude/ -std=gnu11 -Werror -pedantic -pedantic-errors -O3 -c libtvm/tvm.c -o libtvm/tvm.o
clang -Wall -pipe -Iinclude/ -std=gnu11 -Werror -pedantic -pedantic-errors -O3 -c libtvm/tvm_htab.c -o libtvm/tvm_htab.o
clang -Wall -pipe -Iinclude/ -std=gnu11 -Werror -pedantic -pedantic-errors -O3 -c libtvm/tvm_memory.c -o libtvm/tvm_memory.o
clang -Wall -pipe -Iinclude/ -std=gnu11 -Werror -pedantic -pedantic-errors -O3 -c libtvm/tvm_preprocessor.c -o libtvm/tvm_preprocessor.o
clang -Wall -pipe -Iinclude/ -std=gnu11 -Werror -pedantic -pedantic-errors -O3 -c libtvm/tvm_parser.c -o libtvm/tvm_parser.o
clang -Wall -pipe -Iinclude/ -std=gnu11 -Werror -pedantic -pedantic-errors -O3 -c libtvm/tvm_file.c -o libtvm/tvm_file.o
ar rcs lib/libtvm.a libtvm/tvm_program.o libtvm/tvm_lexer.o libtvm/tvm.o libtvm/tvm_htab.o libtvm/tvm_memory.o libtvm/tvm_preprocessor.o libtvm/tvm_parser.o libtvm/tvm_file.o
clang src/tvmi.c -ltvm -Wall -pipe -Iinclude/ -std=gnu11 -Werror -pedantic -pedantic-errors -O3 -Llib/ -o bin/tvmi
clang -Wall -pipe -Iinclude/ -std=gnu11 -Werror -pedantic -pedantic-errors -O3 -c tdb/main.c -o tdb/main.o
clang -Wall -pipe -Iinclude/ -std=gnu11 -Werror -pedantic -pedantic-errors -O3 -c tdb/tdb.c -o tdb/tdb.o
clang tdb/main.o tdb/tdb.o -ltvm -Wall -pipe -Iinclude/ -std=gnu11 -Werror -pedantic -pedantic-errors -O3 -Llib/ -o bin/tdb
Eu realmente gosto quando as bibliotecas C são compiladas assim que saem da caixa, sem ter que instalar pacotes aleatórios
*-dev
ou bagunçar o sistema de construção.
Infelizmente, a biblioteca não contém nenhum teste, então não podemos (imediatamente) ter certeza de que as funções individuais foram traduzidas corretamente, mas ela contém um interpretador de exemplo que podemos usar para explorar a funcionalidade de alto nível.
Assim, sabemos que podemos construí-lo a partir da linha de comando sem muito trabalho. Agora precisamos ter certeza de que nossa caixa
tinyvm
é capaz de montar tudo programaticamente.
É aqui que entram os scripts de construção. Nossa estratégia é que a caixa Rust use o script de construção
build.rs
e a caixa cc
para invocar os comandos equivalentes à nossa chamadamake
... A partir daí, podemos nos conectar a libtvm
partir do Rust como qualquer outra biblioteca nativa.
Você precisará adicionar a caixa
cc
como uma dependência.
$ cargo add --build cc
Updating 'https://github.com/rust-lang/crates.io-index' index
Adding cc v1.0.47 to build-dependencies
E também certifique-se de
build.rs
compilar a partir do código-fonte libtvm
.
// build.rs
use cc::Build;
use std::path::Path;
fn main() {
let tinyvm = Path::new("vendor/tinyvm");
let include = tinyvm.join("include");
let src = tinyvm.join("libtvm");
Build::new()
.warnings(false)
.file(src.join("tvm_file.c"))
.file(src.join("tvm_htab.c"))
.file(src.join("tvm_lexer.c"))
.file(src.join("tvm_memory.c"))
.file(src.join("tvm_parser.c"))
.file(src.join("tvm_preprocessor.c"))
.file(src.join("tvm_program.c"))
.file(src.join("tvm.c"))
.include(&include)
.compile("tvm");
}
Nota
Se você olhou a documentação do cratecc
, pode ter notado um métodoBuild::files()
que aceita um iterador de caminhos. Nós poderia programaticamente descobrir todos os arquivos*.c
dentrovendor/tinyvm/libtvm
, mas desde que estão portando a função de código de um de cada vez, é muito mais fácil de remover as chamadas individuais.file()
como nós porto.
Também precisamos de uma maneira de dizer ao Rust de quais funções ele pode chamar
libtvm
. Isso geralmente é feito escrevendo as definições para cada função em um bloco externo , mas felizmente há uma ferramenta chamada bindgen que pode ler um arquivo de cabeçalho no estilo C e gerar definições para nós.
Vamos gerar ligações de
vendor/tinyvm/include/tvm/tvm.h
.
$ cargo install bindgen
$ bindgen vendor/tinyvm/include/tvm/tvm.h -o src/ffi.rs
$ wc --lines src/ffi.rs
992 src/ffi.rs
Você precisará adicionar um módulo à nossa caixa
ffi
.
// src/lib.rs
#[allow(non_camel_case_types, non_snake_case)]
pub mod ffi;
Olhando para o diretório
src/
em tinyvm
, encontramos o código-fonte do interpretador tinyvm
.
// vendor/tinyvm/src/tvmi.c
#include <stdlib.h>
#include <stdio.h>
#include <tvm/tvm.h>
int main(int argc, char **argv)
{
struct tvm_ctx *vm = tvm_vm_create();
if (vm != NULL && tvm_vm_interpret(vm, argv[1]) == 0)
tvm_vm_run(vm);
tvm_vm_destroy(vm);
return 0;
}
É incrivelmente simples. O que é muito bom, considerando que usaremos esse interpretador como um de nossos exemplos.
Por enquanto, vamos traduzi-lo diretamente para o Rust e colocá-lo no diretório
examples/
.
// examples/tvmi.rs
use std::{env, ffi::CString};
use tinyvm::ffi;
fn main() {
let filename = CString::new(env::args().nth(1).unwrap()).unwrap();
// cast away the `const` because that's what libtvm expects
let filename = filename.as_ptr() as *mut _;
unsafe {
let vm = ffi::tvm_vm_create();
if !vm.is_null() && ffi::tvm_vm_interpret(vm, filename) == 0 {
ffi::tvm_vm_run(vm);
}
ffi::tvm_vm_destroy(vm);
}
}
Como verificação, também podemos iniciar uma máquina virtual e garantir que tudo funcione.
$ cargo run --example tvmi -- vendor/tinyvm/programs/tinyvm/fact.vm
Finished dev [unoptimized + debuginfo] target(s) in 0.02s
Running `target/debug/examples/tvmi vendor/tinyvm/programs/tinyvm/fact.vm`
1
2
6
24
120
720
5040
40320
362880
3628800
Classe!
Frutos mais baixos
Quando você começa com algo assim, é tentador mergulhar nos recursos mais importantes e migrá-los primeiro. Tente resistir a esse desejo. Você pode facilmente morder mais do que pode mastigar e acabar perdendo tempo ou desmoralizando-o e fazendo-o desistir.
Em vez disso, vamos procurar o mais simples.
$ ls libtvm
tvm.c tvm_file.c tvm_htab.c tvm_lexer.c tvm_memory.c tvm_parser.c
tvm_preprocessor.c tvm_program.c
Este arquivo
tvm_htab.
parece promissor. Tenho certeza de que htab
significa "tabela de hash" e a biblioteca padrão Rust já contém uma implementação de alta qualidade. Devemos ser capazes de mudar isso com bastante facilidade.
Vamos dar uma olhada no arquivo de cabeçalho
tvm_htab.h
e verificar com o que estamos lidando.
// vendor/tinyvm/include/tvm/tvm_htab.h
#ifndef TVM_HTAB_H_
#define TVM_HTAB_H_
#define KEY_LENGTH 64
#define HTAB_SIZE 4096
struct tvm_htab_node {
char *key;
int value;
void *valptr;
struct tvm_htab_node *next;
};
struct tvm_htab_ctx {
unsigned int num_nodes;
unsigned int size;
struct tvm_htab_node **nodes;
};
struct tvm_htab_ctx *tvm_htab_create();
void tvm_htab_destroy(struct tvm_htab_ctx *htab);
int tvm_htab_add(struct tvm_htab_ctx *htab, const char *key, int value);
int tvm_htab_add_ref(struct tvm_htab_ctx *htab,
const char *key, const void *valptr, int len);
int tvm_htab_find(struct tvm_htab_ctx *htab, const char *key);
char *tvm_htab_find_ref(struct tvm_htab_ctx *htab, const char *key);
#endif
Parece fácil de implementar. O único problema é que as definições
tvm_htab_ctx
e tvm_htab_node
estão incluídas no arquivo de cabeçalho, o que significa que algum código pode acessar o interior da tabela hash diretamente e não passar pela interface publicada.
Podemos verificar se alguma coisa tem acesso às partes internas da tabela hash movendo temporariamente as definições da estrutura para
tvm_htab.c
e ver se tudo ainda compila.
diff --git a/include/tvm/tvm_htab.h b/include/tvm/tvm_htab.h
index 9feb7a9..e7346b7 100644
--- a/include/tvm/tvm_htab.h
+++ b/include/tvm/tvm_htab.h
@@ -4,18 +4,8 @@
#define KEY_LENGTH 64
#define HTAB_SIZE 4096
-struct tvm_htab_node {
- char *key;
- int value;
- void *valptr;
- struct tvm_htab_node *next;
-};
-
-struct tvm_htab_ctx {
- unsigned int num_nodes;
- unsigned int size;
- struct tvm_htab_node **nodes;
-};
+struct tvm_htab_node;
+struct tvm_htab_ctx;
struct tvm_htab_ctx *tvm_htab_create();
void tvm_htab_destroy(struct tvm_htab_ctx *htab);
E então execute novamente
make
:
$ make
make
clang -Wall -pipe -Iinclude/ -std=gnu11 -Werror -pedantic -pedantic-errors -O3 -c libtvm/tvm_htab.c -o libtvm/tvm_htab.o
ar rcs lib/libtvm.a libtvm/tvm_program.o libtvm/tvm_lexer.o libtvm/tvm.o libtvm/tvm_htab.o libtvm/tvm_memory.o libtvm/tvm_preprocessor.o libtvm/tvm_parser.o libtvm/tvm_file.o
clang src/tvmi.c -ltvm -Wall -pipe -Iinclude/ -std=gnu11 -Werror -pedantic -pedantic-errors -O3 -Llib/ -o bin/tvmi
clang tdb/main.o tdb/tdb.o -ltvm -Wall -pipe -Iinclude/ -std=gnu11 -Werror -pedantic -pedantic-errors -O3 -Llib/ -o bin/tdb
Parece que tudo ainda está funcionando, agora iniciamos a segunda fase; criamos um conjunto idêntico de funções que são usadas nos bastidores
HashMap<K, V>
.
Limitando-nos a um esboço com o mínimo, obtemos:
// src/htab.rs
use std::{
collections::HashMap,
ffi::CString,
os::raw::{c_char, c_int, c_void},
};
#[derive(Debug, Default, Clone, PartialEq)]
pub struct HashTable(pub(crate) HashMap<CString, Item>);
#[derive(Debug, Clone, PartialEq)]
pub(crate) struct Item {
// not sure what to put here yet
}
#[no_mangle]
pub unsafe extern "C" fn tvm_htab_create() -> *mut HashTable {
unimplemented!()
}
#[no_mangle]
pub unsafe extern "C" fn tvm_htab_destroy(htab: *mut HashTable) {
unimplemented!()
}
#[no_mangle]
pub unsafe extern "C" fn tvm_htab_add(
htab: *mut HashTable,
key: *const c_char,
value: c_int,
) -> c_int {
unimplemented!()
}
#[no_mangle]
pub unsafe extern "C" fn tvm_htab_add_ref(
htab: *mut HashTable,
key: *const c_char,
value_ptr: *mut c_void,
length: c_int,
) -> c_int {
unimplemented!()
}
#[no_mangle]
pub unsafe extern "C" fn tvm_htab_find(
htab: *mut HashTable,
key: *const c_char,
) -> c_int {
unimplemented!()
}
#[no_mangle]
pub unsafe extern "C" fn tvm_htab_find_ref(
htab: *mut HashTable,
key: *const c_char,
) -> *mut c_char {
unimplemented!()
}
Você também precisa declarar o módulo
htab
e reexportar suas funções de lib.rs
.
// src/lib.rs
mod htab;
pub use htab::*;
Agora precisamos ter certeza de que o original
tvm_htab.c
não está compilado ou vinculado à biblioteca final, caso contrário, o vinculador nos encontrará com uma parede de erros de símbolo duplicados.
Parede de erros repetindo símbolos
error: linking with `/usr/bin/clang` failed: exit code: 1
|
= note: "/usr/bin/clang" "-Wl,--as-needed" "-Wl,-z,noexecstack" "-m64" "-L" "/home/michael/.rustup/toolchains/nightly-x86_64-unknown-linux-gnu/lib/rustlib/x86_64-unknown-linux-gnu/lib" "/home/michael/Documents/tinyvm-rs/target/debug/deps/tinyvm-599d57f523fdb1a4.17q5thi94e1eoj5i.rcgu.o" "/home/michael/Documents/tinyvm-rs/target/debug/deps/tinyvm-599d57f523fdb1a4.19e8sqirbm56nu8g.rcgu.o" "/home/michael/Documents/tinyvm-rs/target/debug/deps/tinyvm-599d57f523fdb1a4.1g6ljku8dwzpfvhi.rcgu.o" "/home/michael/Documents/tinyvm-rs/target/debug/deps/tinyvm-599d57f523fdb1a4.1h5e5mxmiptpb7iz.rcgu.o" "/home/michael/Documents/tinyvm-rs/target/debug/deps/tinyvm-599d57f523fdb1a4.1herotdop66zv9ot.rcgu.o" "/home/michael/Documents/tinyvm-rs/target/debug/deps/tinyvm-599d57f523fdb1a4.1qbfxpvgd885u6o.rcgu.o" "/home/michael/Documents/tinyvm-rs/target/debug/deps/tinyvm-599d57f523fdb1a4.21psdg8ni4vgdrzk.rcgu.o" "/home/michael/Documents/tinyvm-rs/target/debug/deps/tinyvm-599d57f523fdb1a4.2albhpxlxxvc0ccu.rcgu.o" "/home/michael/Documents/tinyvm-rs/target/debug/deps/tinyvm-599d57f523fdb1a4.2btm2dc9rhjhhna1.rcgu.o" "/home/michael/Documents/tinyvm-rs/target/debug/deps/tinyvm-599d57f523fdb1a4.2kct5ftnkrqqr0mf.rcgu.o" "/home/michael/Documents/tinyvm-rs/target/debug/deps/tinyvm-599d57f523fdb1a4.2lwgg3uosup4mkh0.rcgu.o" "/home/michael/Documents/tinyvm-rs/target/debug/deps/tinyvm-599d57f523fdb1a4.2xduj46e9sw5vuan.rcgu.o" "/home/michael/Documents/tinyvm-rs/target/debug/deps/tinyvm-599d57f523fdb1a4.35h8y7f23ua1qnz0.rcgu.o" "/home/michael/Documents/tinyvm-rs/target/debug/deps/tinyvm-599d57f523fdb1a4.3cgfdtku63ltd8oc.rcgu.o" "/home/michael/Documents/tinyvm-rs/target/debug/deps/tinyvm-599d57f523fdb1a4.3ot768hzkzzy7r76.rcgu.o" "/home/michael/Documents/tinyvm-rs/target/debug/deps/tinyvm-599d57f523fdb1a4.3u2xnetcch8f2o02.rcgu.o" "/home/michael/Documents/tinyvm-rs/target/debug/deps/tinyvm-599d57f523fdb1a4.4ldrdjvfzk58myrv.rcgu.o" "/home/michael/Documents/tinyvm-rs/target/debug/deps/tinyvm-599d57f523fdb1a4.4omnum6bdjqsrq8b.rcgu.o" "/home/michael/Documents/tinyvm-rs/target/debug/deps/tinyvm-599d57f523fdb1a4.4s8ch4ccmewulj22.rcgu.o" "/home/michael/Documents/tinyvm-rs/target/debug/deps/tinyvm-599d57f523fdb1a4.4syl3x2rb8328h8x.rcgu.o" "/home/michael/Documents/tinyvm-rs/target/debug/deps/tinyvm-599d57f523fdb1a4.532awiysf0h9r50f.rcgu.o" "/home/michael/Documents/tinyvm-rs/target/debug/deps/tinyvm-599d57f523fdb1a4.5b2qwmmtc5pvnbh.rcgu.o" "/home/michael/Documents/tinyvm-rs/target/debug/deps/tinyvm-599d57f523fdb1a4.dfjs079cp9si4o5.rcgu.o" "/home/michael/Documents/tinyvm-rs/target/debug/deps/tinyvm-599d57f523fdb1a4.qxp6yb2gjpj0v6n.rcgu.o" "/home/michael/Documents/tinyvm-rs/target/debug/deps/tinyvm-599d57f523fdb1a4.xz7ld20yvprst1r.rcgu.o" "/home/michael/Documents/tinyvm-rs/target/debug/deps/tinyvm-599d57f523fdb1a4.z35ukhvchmmby1c.rcgu.o" "-o" "/home/michael/Documents/tinyvm-rs/target/debug/deps/tinyvm-599d57f523fdb1a4" "/home/michael/Documents/tinyvm-rs/target/debug/deps/tinyvm-599d57f523fdb1a4.1d7wvlwdjap8p3g4.rcgu.o" "-Wl,--gc-sections" "-pie" "-Wl,-zrelro" "-Wl,-znow" "-nodefaultlibs" "-L" "/home/michael/Documents/tinyvm-rs/target/debug/deps" "-L" "/home/michael/Documents/tinyvm-rs/target/debug/build/tinyvm-3f1a2766f78b5580/out" "-L" "/home/michael/.rustup/toolchains/nightly-x86_64-unknown-linux-gnu/lib/rustlib/x86_64-unknown-linux-gnu/lib" "-Wl,-Bstatic" "-Wl,--whole-archive" "-ltvm" "-Wl,--no-whole-archive" "/home/michael/.rustup/toolchains/nightly-x86_64-unknown-linux-gnu/lib/rustlib/x86_64-unknown-linux-gnu/lib/libtest-a39a3e9a77b17f55.rlib" "/home/michael/.rustup/toolchains/nightly-x86_64-unknown-linux-gnu/lib/rustlib/x86_64-unknown-linux-gnu/lib/libterm-97a69cd310ff0925.rlib" "/home/michael/.rustup/toolchains/nightly-x86_64-unknown-linux-gnu/lib/rustlib/x86_64-unknown-linux-gnu/lib/libgetopts-66a42b1d94e3e6f9.rlib" "/home/michael/.rustup/toolchains/nightly-x86_64-unknown-linux-gnu/lib/rustlib/x86_64-unknown-linux-gnu/lib/libunicode_width-dd7761d848144e0d.rlib" "/home/michael/.rustup/toolchains/nightly-x86_64-unknown-linux-gnu/lib/rustlib/x86_64-unknown-linux-gnu/lib/librustc_std_workspace_std-f722acdb78755ba0.rlib" "-Wl,--start-group" "/home/michael/.rustup/toolchains/nightly-x86_64-unknown-linux-gnu/lib/rustlib/x86_64-unknown-linux-gnu/lib/libstd-974c3c08f6def4b3.rlib" "/home/michael/.rustup/toolchains/nightly-x86_64-unknown-linux-gnu/lib/rustlib/x86_64-unknown-linux-gnu/lib/libpanic_unwind-eb49676f33a2c8a6.rlib" "/home/michael/.rustup/toolchains/nightly-x86_64-unknown-linux-gnu/lib/rustlib/x86_64-unknown-linux-gnu/lib/libhashbrown-7ae0446feecc60f2.rlib" "/home/michael/.rustup/toolchains/nightly-x86_64-unknown-linux-gnu/lib/rustlib/x86_64-unknown-linux-gnu/lib/librustc_std_workspace_alloc-2de299b65d7f5721.rlib" "/home/michael/.rustup/toolchains/nightly-x86_64-unknown-linux-gnu/lib/rustlib/x86_64-unknown-linux-gnu/lib/libbacktrace-64514775bc06309a.rlib" "/home/michael/.rustup/toolchains/nightly-x86_64-unknown-linux-gnu/lib/rustlib/x86_64-unknown-linux-gnu/lib/libbacktrace_sys-1ed8aa185c63b9a5.rlib" "/home/michael/.rustup/toolchains/nightly-x86_64-unknown-linux-gnu/lib/rustlib/x86_64-unknown-linux-gnu/lib/librustc_demangle-a839df87f563fba5.rlib" "/home/michael/.rustup/toolchains/nightly-x86_64-unknown-linux-gnu/lib/rustlib/x86_64-unknown-linux-gnu/lib/libunwind-8e726bdc2018d836.rlib" "/home/michael/.rustup/toolchains/nightly-x86_64-unknown-linux-gnu/lib/rustlib/x86_64-unknown-linux-gnu/lib/libcfg_if-5285f42cbadf207d.rlib" "/home/michael/.rustup/toolchains/nightly-x86_64-unknown-linux-gnu/lib/rustlib/x86_64-unknown-linux-gnu/lib/liblibc-b0362d20f8aa58fa.rlib" "/home/michael/.rustup/toolchains/nightly-x86_64-unknown-linux-gnu/lib/rustlib/x86_64-unknown-linux-gnu/lib/liballoc-f3dd7051708453a4.rlib" "/home/michael/.rustup/toolchains/nightly-x86_64-unknown-linux-gnu/lib/rustlib/x86_64-unknown-linux-gnu/lib/librustc_std_workspace_core-83744846c43307ce.rlib" "/home/michael/.rustup/toolchains/nightly-x86_64-unknown-linux-gnu/lib/rustlib/x86_64-unknown-linux-gnu/lib/libcore-d5565a3a0f4cfe21.rlib" "-Wl,--end-group" "/home/michael/.rustup/toolchains/nightly-x86_64-unknown-linux-gnu/lib/rustlib/x86_64-unknown-linux-gnu/lib/libcompiler_builtins-ea790e85415e3bbf.rlib" "-Wl,-Bdynamic" "-ldl" "-lrt" "-lpthread" "-lgcc_s" "-lc" "-lm" "-lrt" "-lpthread" "-lutil" "-lutil" "-fuse-ld=lld"
= note: ld.lld: error: duplicate symbol: tvm_htab_create
>>> defined at htab.rs:14 (src/htab.rs:14)
>>> /home/michael/Documents/tinyvm-rs/target/debug/deps/tinyvm-599d57f523fdb1a4.5b2qwmmtc5pvnbh.rcgu.o:(tvm_htab_create)
>>> defined at tvm_htab.c:23 (vendor/tinyvm/libtvm/tvm_htab.c:23)
>>> tvm_htab.o:(.text.tvm_htab_create+0x0) in archive /home/michael/Documents/tinyvm-rs/target/debug/build/tinyvm-3f1a2766f78b5580/out/libtvm.a
ld.lld: error: duplicate symbol: tvm_htab_destroy
>>> defined at htab.rs:17 (src/htab.rs:17)
>>> /home/michael/Documents/tinyvm-rs/target/debug/deps/tinyvm-599d57f523fdb1a4.5b2qwmmtc5pvnbh.rcgu.o:(tvm_htab_destroy)
>>> defined at tvm_htab.c:35 (vendor/tinyvm/libtvm/tvm_htab.c:35)
>>> tvm_htab.o:(.text.tvm_htab_destroy+0x0) in archive /home/michael/Documents/tinyvm-rs/target/debug/build/tinyvm-3f1a2766f78b5580/out/libtvm.a
ld.lld: error: duplicate symbol: tvm_htab_add_ref
>>> defined at htab.rs:29 (src/htab.rs:29)
>>> /home/michael/Documents/tinyvm-rs/target/debug/deps/tinyvm-599d57f523fdb1a4.5b2qwmmtc5pvnbh.rcgu.o:(tvm_htab_add_ref)
>>> defined at tvm_htab.c:160 (vendor/tinyvm/libtvm/tvm_htab.c:160)
>>> tvm_htab.o:(.text.tvm_htab_add_ref+0x0) in archive /home/michael/Documents/tinyvm-rs/target/debug/build/tinyvm-3f1a2766f78b5580/out/libtvm.a
ld.lld: error: duplicate symbol: tvm_htab_add
>>> defined at htab.rs:20 (src/htab.rs:20)
>>> /home/michael/Documents/tinyvm-rs/target/debug/deps/tinyvm-599d57f523fdb1a4.5b2qwmmtc5pvnbh.rcgu.o:(tvm_htab_add)
>>> defined at tvm_htab.c:147 (vendor/tinyvm/libtvm/tvm_htab.c:147)
>>> tvm_htab.o:(.text.tvm_htab_add+0x0) in archive /home/michael/Documents/tinyvm-rs/target/debug/build/tinyvm-3f1a2766f78b5580/out/libtvm.a
ld.lld: error: duplicate symbol: tvm_htab_find
>>> defined at htab.rs:39 (src/htab.rs:39)
>>> /home/michael/Documents/tinyvm-rs/target/debug/deps/tinyvm-599d57f523fdb1a4.5b2qwmmtc5pvnbh.rcgu.o:(tvm_htab_find)
>>> defined at tvm_htab.c:189 (vendor/tinyvm/libtvm/tvm_htab.c:189)
>>> tvm_htab.o:(.text.tvm_htab_find+0x0) in archive /home/michael/Documents/tinyvm-rs/target/debug/build/tinyvm-3f1a2766f78b5580/out/libtvm.a
ld.lld: error: duplicate symbol: tvm_htab_find_ref
>>> defined at htab.rs:47 (src/htab.rs:47)
>>> /home/michael/Documents/tinyvm-rs/target/debug/deps/tinyvm-599d57f523fdb1a4.5b2qwmmtc5pvnbh.rcgu.o:(tvm_htab_find_ref)
>>> defined at tvm_htab.c:199 (vendor/tinyvm/libtvm/tvm_htab.c:199)
>>> tvm_htab.o:(.text.tvm_htab_find_ref+0x0) in archive /home/michael/Documents/tinyvm-rs/target/debug/build/tinyvm-3f1a2766f78b5580/out/libtvm.a
clang: error: linker command failed with exit code 1 (use -v to see invocation)
error: aborting due to previous error
error: could not compile `tinyvm`.
A correção é bem simples.
diff --git a/build.rs b/build.rs
index 6f274c8..af9d467 100644
--- a/build.rs
+++ b/build.rs
@@ -9,7 +9,6 @@ fn main() {
Build::new()
.warnings(false)
.file(src.join("tvm_file.c"))
- .file(src.join("tvm_htab.c"))
.file(src.join("tvm_lexer.c"))
.file(src.join("tvm_memory.c"))
.file(src.join("tvm_parser.c"))
E tentar executar o exemplo
tvmi
novamente trava, como você esperaria de um programa completo unimplemented!()
.
$ cargo run --example tvmi -- vendor/tinyvm/programs/tinyvm/fact.vm
Finished dev [unoptimized + debuginfo] target(s) in 0.02s
Running `target/debug/examples/tvmi vendor/tinyvm/programs/tinyvm/fact.vm`
thread 'main' panicked at 'not yet implemented', src/htab.rs:14:57
note: run with `RUST_BACKTRACE=1` environment variable to display a backtrace.
Ao adicionar suporte FFI para um novo tipo, o lugar mais fácil para começar é com o construtor e o destruidor.
O
código Info C só pode acessar nossa tabela hash por meio de um ponteiro, portanto, precisamos alocar um deles no heap e, em seguida, transferir a propriedade desse objeto alocado no heap para o chamador.
// src/htab.rs
#[no_mangle]
pub unsafe extern "C" fn tvm_htab_create() -> *mut HashTable {
let hashtable = Box::new(HashTable::default());
Box::into_raw(hashtable)
}
#[no_mangle]
pub unsafe extern "C" fn tvm_htab_destroy(htab: *mut HashTable) {
if htab.is_null() {
// nothing to free
return;
}
let hashtable = Box::from_raw(htab);
// explicitly destroy the hashtable
drop(hashtable);
}
Aviso
É importante que os chamadoresHashTable
só matem com a funçãotvm_htab_destroy ()
!
Se não o fizerem e tentarem ligarfree()
diretamente, é quase certo que teremos uma situação ruim. Na melhor das hipóteses, isso levará a um grande vazamento de memória, mas também é bem possível que nossoBox
em Rust não use o mesmo feixe quemalloc()
efree ()
, o que significa que a liberação do objeto Rust de C pode danificar a pilha e deixá-la em um estado quebrado.
Adicionar itens a um hashmap é quase tão fácil de implementar.
// src/hmap.rs
#[derive(Debug, Clone, PartialEq)]
pub(crate) struct Item {
/// An integer value.
value: c_int,
/// An opaque value used with [`tvm_htab_add_ref()`].
///
/// # Safety
///
/// Storing the contents of a `void *` in a `Vec<u8>` *would* normally
/// result in alignment issues, but we've got access to the `libtvm` source
/// code and know it will only ever store `char *` strings.
opaque_value: Vec<u8>,
}
impl Item {
pub(crate) fn integer(value: c_int) -> Item {
Item {
value,
opaque_value: Vec::new(),
}
}
pub(crate) fn opaque<V>(opaque_value: V) -> Item
where
V: Into<Vec<u8>>,
{
Item {
value: 0,
opaque_value: opaque_value.into(),
}
}
pub(crate) fn from_void(pointer: *mut c_void, length: c_int) -> Item {
// we need to create an owned copy of the value
let opaque_value = if pointer.is_null() {
Vec::new()
} else {
unsafe {
std::slice::from_raw_parts(pointer as *mut u8, length as usize)
.to_owned()
}
};
Item::opaque(opaque_value)
}
}
#[no_mangle]
pub unsafe extern "C" fn tvm_htab_add(
htab: *mut HashTable,
key: *const c_char,
value: c_int,
) -> c_int {
let hashtable = &mut *htab;
let key = CStr::from_ptr(key).to_owned();
hashtable.0.insert(key, Item::integer(value));
// the only time insertion can fail is if allocation fails. In that case
// we'll abort the process anyway, so if this function returns we can
// assume it was successful (0 = success).
0
}
#[no_mangle]
pub unsafe extern "C" fn tvm_htab_add_ref(
htab: *mut HashTable,
key: *const c_char,
value_ptr: *mut c_void,
length: c_int,
) -> c_int {
let hashtable = &mut *htab;
let key = CStr::from_ptr(key).to_owned();
hashtable.0.insert(key, Item::from_void(value_ptr, length));
0
}
,CString
,String
, -,*const c_char
,String
Rust , UTF-8.
,CStr
&str
,String
, ASCII, ,unwrap()
,CString
.
Duas funções
*_find()
podem ser delegadas diretamente à interna HashMap<CString, Item>
.
O único lugar a ter cuidado é garantir que o valor correto seja retornado quando o elemento não puder ser encontrado. Nesse caso, olhando
tvm_htab.c
podemos ver o que tvm_htab_find()
retorna −1
e o que tvm_htab_find_ref()
retorna NULL
.
// src/hmap.rs
#[no_mangle]
pub unsafe extern "C" fn tvm_htab_find(
htab: *mut HashTable,
key: *const c_char,
) -> c_int {
let hashtable = &mut *htab;
let key = CStr::from_ptr(key);
match hashtable.get(key) {
Some(item) => item.value,
None => -1,
}
}
#[no_mangle]
pub unsafe extern "C" fn tvm_htab_find_ref(
htab: *mut HashTable,
key: *const c_char,
) -> *mut c_char {
let hashtable = &mut *htab;
let key = CStr::from_ptr(key);
match hashtable.0.get(key) {
Some(item) => item.value_ptr as *mut c_char,
None => ptr::null_mut(),
}
}
Agora que implementamos a funcionalidade de stub, tudo deve funcionar novamente.
A maneira mais fácil de testar isso é rodar nosso exemplo.
cargo run --example tvmi -- vendor/tinyvm/programs/tinyvm/fact.vm
Finished dev [unoptimized + debuginfo] target(s) in 0.02s
Running `target/debug/examples/tvmi vendor/tinyvm/programs/tinyvm/fact.vm`
1
2
6
24
120
720
5040
40320
362880
3628800
E para verificar, podemos examiná-lo
valgrind
para ter certeza de que não há vazamentos de memória ou qualquer coisa complicada com ponteiros.
$ valgrind target/debug/examples/tvmi vendor/tinyvm/programs/tinyvm/fact.vm
==1492== Memcheck, a memory error detector
==1492== Copyright (C) 2002-2017, and GNU GPL'd, by Julian Seward et al.
==1492== Using Valgrind-3.15.0 and LibVEX; rerun with -h for copyright info
==1492== Command: target/debug/examples/tvmi vendor/tinyvm/programs/tinyvm/fact.vm
==1492==
1
2
6
24
120
720
5040
40320
362880
3628800
==1492==
==1492== HEAP SUMMARY:
==1492== in use at exit: 0 bytes in 0 blocks
==1492== total heap usage: 270 allocs, 270 frees, 67,129,392 bytes allocated
==1492==
==1492== All heap blocks were freed -- no leaks are possible
==1492==
==1492== For lists of detected and suppressed errors, rerun with: -s
==1492== ERROR SUMMARY: 0 errors from 0 contexts (suppressed: 0 from 0)
Sucesso!
Implementação de pré-processamento de dados
A máquina virtual
tinyvm
usa uma forma simplificada de assembler semelhante ao tradicional Intel x86 assembler. A primeira etapa na análise do assembler de tinyvm é executar um pré-processador que interpreta instruções %include filename
e instruções %define identifier value
.
Esse tipo de manipulação de texto é muito mais fácil com tipos
&str
em Rust, então vamos dar uma olhada na interface que nossa caixa deve implementar.
// vendor/tinyvm/include/tvm/tvm_preprocessor.h
#ifndef TVM_PREPROCESSOR_H_
#define TVM_PREPROCESSOR_H_
#include "tvm_htab.h"
int tvm_preprocess(char **src, int *src_len, struct tvm_htab_ctx *defines);
#endif
Usar
char **
ambos int *
para variáveis src
e src_len
pode parecer um pouco estranho no início, mas se você escreveu o equivalente em Rust, você acabaria com algo assim:
fn tvm_preprocess(
src: String,
defines: &mut HashTable,
) -> Result<String, PreprocessorError> {
...
}
O código C apenas usa os parâmetros de saída para substituir a string
src
no lugar, porque ele não pode retornar uma nova linha ou um código de erro.
Antes de fazer qualquer outra coisa, você precisa escrever um teste para
tvm_preprocess()
. Desta forma, podemos garantir que nossa função Rust é funcionalmente equivalente ao original.
Interagimos com o sistema de arquivos, portanto, precisaremos retirar a caixa de arquivos temporários .
$ cargo add --dev tempfile
Updating 'https://github.com/rust-lang/crates.io-index' index
Adding tempfile v3.1.0 to dev-dependencies
Também precisamos de um engradado
libc
, pois estaremos passando linhas libtvm
que ele pode precisar para liberar.
cargo add libc
Updating 'https://github.com/rust-lang/crates.io-index' index
Adding libc v0.2.66 to dev-dependencies
Olhando o código-fonte, podemos ver que a função
tvm_preprocess()
continuará a permitir %include
e %define
enquanto for não.
Primeiro, vamos criar um teste para garantir que o pré-processador funcione
%define
. Sabemos que este código já está funcionando (afinal, é um código de origem tinyvm
), então não deve haver nenhuma surpresa.
// src/preprocessing.rs
#[cfg(test)]
mod tests {
use crate::ffi;
use std::{
ffi::{CStr, CString},
io::Write,
os::raw::c_int,
};
#[test]
fn find_all_defines() {
let src = "%define true 1\nsome random text\n%define FOO_BAR -42\n";
let original_length = src.len();
let src = CString::new(src).unwrap();
unsafe {
// get a copy of `src` that was allocated using C's malloc
let mut src = libc::strdup(src.as_ptr());
let mut len = original_length as c_int;
let defines = ffi::tvm_htab_create();
let ret = ffi::tvm_preprocess(&mut src, &mut len, defines);
// preprocessing should have been successful
assert_eq!(ret, 0);
// make sure the define lines were removed
let preprocessed = CStr::from_ptr(src).to_bytes();
let preprocessed =
std::str::from_utf8(&preprocessed[..len as usize]).unwrap();
assert_eq!(preprocessed, "\nsome random text\n\n");
// make sure the "true" and "FOO_BAR" defines were set
let true_define =
ffi::tvm_htab_find_ref(defines, b"true\0".as_ptr().cast());
let got = CStr::from_ptr(true_define).to_str().unwrap();
assert_eq!(got, "1");
let foo_bar =
ffi::tvm_htab_find_ref(defines, b"FOO_BAR\0".as_ptr().cast());
let got = CStr::from_ptr(foo_bar).to_str().unwrap();
assert_eq!(got, "-42");
// clean up our hashtable and copied source text
ffi::tvm_htab_destroy(defines);
libc::free(src.cast());
}
}
}
45 linhas é muito mais do que eu normalmente gosto em testes, mas é preciso uma boa quantidade de código extra para converter entre linhas C.
Também precisamos verificar, incluindo mais um arquivo.
// src/preprocessing.rs
#[cfg(test)]
mod tests {
...
#[test]
fn include_another_file() {
const TOP_LEVEL: &str = "first line\n%include nested\nlast line\n";
const NESTED: &str = "nested\n";
// the preprocessor imports files from the filesystem, so we need to
// copy NESTED to a temporary location
let mut nested = NamedTempFile::new().unwrap();
nested.write_all(NESTED.as_bytes()).unwrap();
let nested_filename = nested.path().display().to_string();
// substitute the full path to the "nested" file
let top_level_src = TOP_LEVEL.replace("nested", &nested_filename);
std::fs::write(&nested, NESTED).unwrap();
unsafe {
let top_level_src = CString::new(top_level_src).unwrap();
// create a copy of the top_level_src which can be freed by C
let mut src = libc::strdup(top_level_src.as_ptr());
let mut len = libc::strlen(src) as c_int;
let defines = ffi::tvm_htab_create();
// after all that setup code we can *finally* call the preprocessor
let ret = ffi::tvm_preprocess(&mut src, &mut len, defines);
assert_eq!(ret, 0);
// make sure the define and import lines were removed
let preprocessed = CStr::from_ptr(src).to_bytes();
let got =
std::str::from_utf8(&preprocessed[..len as usize]).unwrap();
// after preprocessing, all include and define lines should have
// been removed
assert_eq!(got, "first line\nnested\nlast line\n");
ffi::tvm_htab_destroy(defines);
libc::free(src.cast());
}
}
Observação
Como uma observação lateral, o teste foi originalmente escrito para aninhar tudo em três camadas de profundidade (por exemplo,top_level.vm
inclui nested.vm, que inclui really_nested.vm) para garantir que lida com mais de um nível%include
, mas independentemente de Conforme foi escrito, o teste continuou com falha em seg.
Então tentei executar o binário C originaltvmi
...
$ cd vendor/tinyvm/ $ cat top_level.vm %include nested $ cat nested.vm %include really_nested $ cat really_nested.vm Hello World $ ./bin/tvmi top_level.vm [1] 10607 segmentation fault (core dumped) ./bin/tvmi top_level.vm
Acontece que o tinyvm original falha por algum motivo quando você tem várias camadasinclude
...
Agora temos alguns testes para começar a implementar
tvm_preprocess()
.
Primeiro, você precisa determinar o tipo de erro.
// src/preprocessing.rs
#[derive(Debug)]
pub enum PreprocessingError {
FailedInclude {
name: String,
inner: IoError,
},
DuplicateDefine {
name: String,
original_value: String,
new_value: String,
},
EmptyDefine,
DefineWithoutValue(String),
}
Olhando para as funções process_includes () e process_derives () , parece que eles fazem a varredura de uma string procurando por uma diretiva específica, e então substituem aquela string por outra (o conteúdo do arquivo, ou nada se a linha deveria ser deletada).
Precisamos ser capazes de extrair essa lógica em um auxiliar e evitar duplicações desnecessárias.
// src/preprocessing.rs
/// Scan through the input string looking for a line starting with some
/// directive, using a callback to figure out what to replace the directive line
/// with.
fn process_line_starting_with_directive<F>(
mut src: String,
directive: &str,
mut replace_line: F,
) -> Result<(String, usize), PreprocessingError>
where
F: FnMut(&str) -> Result<String, PreprocessingError>,
{
// try to find the first instance of the directive
let directive_delimiter = match src.find(directive) {
Some(ix) => ix,
None => return Ok((src, 0)),
};
// calculate the span from the directive to the end of the line
let end_ix = src[directive_delimiter..]
.find('\n')
.map(|ix| ix + directive_delimiter)
.unwrap_or(src.len());
// the rest of the line after the directive
let directive_line =
src[directive_delimiter + directive.len()..end_ix].trim();
// use the callback to figure out what we should replace the line with
let replacement = replace_line(directive_line)?;
// remove the original line
let _ = src.drain(directive_delimiter..end_ix);
// then insert our replacement
src.insert_str(directive_delimiter, &replacement);
Ok((src, 1))
}
Agora temos um auxiliar
process_line_starting_with_directive()
, portanto, podemos implementar um analisador %include
.
// src/preprocessing.rs
fn process_includes(
src: String,
) -> Result<(String, usize), PreprocessingError> {
const TOK_INCLUDE: &str = "%include";
process_line_starting_with_directive(src, TOK_INCLUDE, |line| {
std::fs::read_to_string(line).map_err(|e| {
PreprocessingError::FailedInclude {
name: line.to_string(),
inner: e,
}
})
})
}
Infelizmente, o analisador% define é um pouco mais complexo.
// src/preprocessing.rs
n process_defines(
src: String,
defines: &mut HashTable,
) -> Result<(String, usize), PreprocessingError> {
const TOK_DEFINE: &str = "%define";
process_line_starting_with_directive(src, TOK_DEFINE, |line| {
parse_define(line, defines)?;
Ok(String::new())
})
}
fn parse_define(
line: &str,
defines: &mut HashTable,
) -> Result<(), PreprocessingError> {
if line.is_empty() {
return Err(PreprocessingError::EmptyDefine);
}
// The syntax is "%define key value", so after removing the leading
// "%define" everything after the next space is the value
let first_space = line.find(' ').ok_or_else(|| {
PreprocessingError::DefineWithoutValue(line.to_string())
})?;
// split the rest of the line into key and value
let (key, value) = line.split_at(first_space);
let value = value.trim();
match defines.0.entry(
CString::new(key).expect("The text shouldn't contain null bytes"),
) {
// the happy case, this symbol hasn't been defined before so we can just
// insert it.
Entry::Vacant(vacant) => {
vacant.insert(Item::opaque(value));
},
// looks like this key has already been defined, report an error
Entry::Occupied(occupied) => {
return Err(PreprocessingError::DuplicateDefine {
name: key.to_string(),
original_value: occupied
.get()
.opaque_value_str()
.unwrap_or("<invalid>")
.to_string(),
new_value: value.to_string(),
});
},
}
Ok(())
}
Para acessar o texto em nossa tabela hash, precisaremos fornecer ao elemento
Item
alguns métodos auxiliares:
// src/htab.rs
impl Item {
...
pub(crate) fn opaque_value(&self) -> &[u8] { &self.opaque_value }
pub(crate) fn opaque_value_str(&self) -> Option<&str> {
std::str::from_utf8(self.opaque_value()).ok()
}
}
É uma boa ideia adicionar mais alguns testes neste momento.
// src/preprocessing.rs
#[cfg(test)]
mod tests {
...
#[test]
fn empty_string() {
let src = String::from("");
let mut hashtable = HashTable::default();
let (got, replacements) = process_defines(src, &mut hashtable).unwrap();
assert!(got.is_empty());
assert_eq!(replacements, 0);
assert!(hashtable.0.is_empty());
}
#[test]
fn false_percent() {
let src = String::from("this string contains a % symbol");
let mut hashtable = HashTable::default();
let (got, replacements) =
process_defines(src.clone(), &mut hashtable).unwrap();
assert_eq!(got, src);
assert_eq!(replacements, 0);
assert!(hashtable.0.is_empty());
}
#[test]
fn define_without_key_and_value() {
let src = String::from("%define\n");
let mut hashtable = HashTable::default();
let err = process_defines(src.clone(), &mut hashtable).unwrap_err();
match err {
PreprocessingError::EmptyDefine => {},
other => panic!("Expected EmptyDefine, found {:?}", other),
}
}
#[test]
fn define_without_value() {
let src = String::from("%define key\n");
let mut hashtable = HashTable::default();
let err = process_defines(src.clone(), &mut hashtable).unwrap_err();
match err {
PreprocessingError::DefineWithoutValue(key) => {
assert_eq!(key, "key")
},
other => panic!("Expected DefineWithoutValue, found {:?}", other),
}
}
#[test]
fn valid_define() {
let src = String::from("%define key value\n");
let mut hashtable = HashTable::default();
let (got, num_defines) = process_defines(src.clone(), &mut hashtable).unwrap();
assert_eq!(got, "\n");
assert_eq!(num_defines, 1);
assert_eq!(hashtable.0.len(), 1);
let key = CString::new("key").unwrap();
let item = hashtable.0.get(&key).unwrap();
assert_eq!(item.opaque_value_str().unwrap(), "value");
}
}
Neste ponto, reproduzimos a maior parte da lógica de pré-processamento, então agora precisamos apenas de uma função que continuará a expandir as instruções
%include
e o processo %define
até que não haja mais.
// src/preprocessing.rs
pub fn preprocess(
src: String,
defines: &mut HashTable,
) -> Result<String, PreprocessingError> {
let mut src = src;
loop {
let (modified, num_includes) = process_includes(src)?;
let (modified, num_defines) = process_defines(modified, defines)?;
if num_includes + num_defines == 0 {
return Ok(modified);
}
src = modified;
}
}
Claro, esse recurso
preprocess()
está disponível apenas para Rust. Precisamos criar extern "C" fn
um que traduza argumentos de tipos C em algo que Rust possa manipular e, em seguida, traduza de volta para C.
// src/preprocessing.rs
#[no_mangle]
pub unsafe extern "C" fn tvm_preprocess(
src: *mut *mut c_char,
src_len: *mut c_int,
defines: *mut tvm_htab_ctx,
) -> c_int {
if src.is_null() || src_len.is_null() || defines.is_null() {
return -1;
}
// Safety: This assumes the tvm_htab_ctx is actually our ported HashTable
let defines = &mut *(defines as *mut HashTable);
// convert the input string to an owned Rust string so it can be
// preprocessed
let rust_src = match CStr::from_ptr(*src).to_str() {
Ok(s) => s.to_string(),
// just error out if it's not valid UTF-8
Err(_) => return -1,
};
match preprocess(rust_src, defines) {
Ok(s) => {
let preprocessed = CString::new(s).unwrap();
// create a copy of the preprocessed string that can be free'd by C
// and use the output arguments to pass it to the caller
*src = libc::strdup(preprocessed.as_ptr());
// the original C implementation didn't add a null terminator to the
// preprocessed string, so we're required to set the length as well.
*src_len = libc::strlen(*src) as c_int;
// returning 0 indicates success
0
},
// tell the caller "an error occurred"
Err(_) => -1,
}
}
Dica Você
deve ter notado que nossa função tvm_preprocess () não tem nenhuma lógica de pré-processamento e é mais como um adaptador para traduzir argumentos e valores de retorno e garantir a propagação correta do erro.
Isso não é coincidência.
O segredo para codificar FFI é escrever o mínimo possível e evitar truques inteligentes . Ao contrário da maioria dos códigos Rust, erros em tais funções de interoperabilidade podem levar a erros na lógica e na memória.
Criar um wrapper fino em torno de nossa funçãopreprocess()
também torna as coisas mais fáceis: quando a maior parte da base de código é escrita em Rust, podemos remover o wrapper e chamarpreprocess()
diretamente.
A função agora está
tvm_preprocess()
definida e devemos estar prontos para prosseguir.
Compiling tinyvm v0.1.0 (/home/michael/Documents/tinyvm-rs)
error: linking with `/usr/bin/clang` failed: exit code: 1
|
= note: "/usr/bin/clang" "-Wl,--as-needed" "-Wl,-z,noexecstack" "-m64" "-L" "/home/michael/.rustup/toolchains/nightly-x86_64-unknown-linux-gnu/lib/rustlib/x86_64-unknown-linux-gnu/lib" "/home/michael/Documents/tinyvm-rs/target/debug/deps/tinyvm-8eca24ff9a1cde88.13h6j6k0dzqf6zi2.rcgu.o" "/home/michael/Documents/tinyvm-rs/target/debug/deps/tinyvm-8eca24ff9a1cde88.13l2b4uvr7p3ht4k.rcgu.o" "/home/michael/Documents/tinyvm-rs/target/debug/deps/tinyvm-8eca24ff9a1cde88.14bdbjhozo3id49g.rcgu.o" "/home/michael/Documents/tinyvm-rs/target/debug/deps/tinyvm-8eca24ff9a1cde88.14fw2gyd6mrq5730.rcgu.o" "/home/michael/Documents/tinyvm-rs/target/debug/deps/tinyvm-8eca24ff9a1cde88.19xc7n0bb25uaxgk.rcgu.o" "/home/michael/Documents/tinyvm-rs/target/debug/deps/tinyvm-8eca24ff9a1cde88.1duzy573vjvyihco.rcgu.o" "/home/michael/Documents/tinyvm-rs/target/debug/deps/tinyvm-8eca24ff9a1cde88.1e0yejy24qufh7ie.rcgu.o" "/home/michael/Documents/tinyvm-rs/target/debug/deps/tinyvm-8eca24ff9a1cde88.1k4xuir9ezt4vkzp.rcgu.o" "/home/michael/Documents/tinyvm-rs/target/debug/deps/tinyvm-8eca24ff9a1cde88.1mqdnrarww1zjlt.rcgu.o" "/home/michael/Documents/tinyvm-rs/target/debug/deps/tinyvm-8eca24ff9a1cde88.1ubflbxzxkx7grpn.rcgu.o" "/home/michael/Documents/tinyvm-rs/target/debug/deps/tinyvm-8eca24ff9a1cde88.1vtvcpzzusyku3mk.rcgu.o" "/home/michael/Documents/tinyvm-rs/target/debug/deps/tinyvm-8eca24ff9a1cde88.1wal3ebwyfg4qllf.rcgu.o" "/home/michael/Documents/tinyvm-rs/target/debug/deps/tinyvm-8eca24ff9a1cde88.235k75fk09i43ba3.rcgu.o" "/home/michael/Documents/tinyvm-rs/target/debug/deps/tinyvm-8eca24ff9a1cde88.253rt7mnjcp3n8ex.rcgu.o" "/home/michael/Documents/tinyvm-rs/target/debug/deps/tinyvm-8eca24ff9a1cde88.27phuscrye2lmkyq.rcgu.o" "/home/michael/Documents/tinyvm-rs/target/debug/deps/tinyvm-8eca24ff9a1cde88.2bwv51h7gucjizh0.rcgu.o" "/home/michael/Documents/tinyvm-rs/target/debug/deps/tinyvm-8eca24ff9a1cde88.2ghuai4hs88aroml.rcgu.o" "/home/michael/Documents/tinyvm-rs/target/debug/deps/tinyvm-8eca24ff9a1cde88.2gqnd9h4nmhvgxbn.rcgu.o" "/home/michael/Documents/tinyvm-rs/target/debug/deps/tinyvm-8eca24ff9a1cde88.2hjvtf620gtog0qz.rcgu.o" "/home/michael/Documents/tinyvm-rs/target/debug/deps/tinyvm-8eca24ff9a1cde88.2hq7kc2w3vix8i5q.rcgu.o" "/home/michael/Documents/tinyvm-rs/target/debug/deps/tinyvm-8eca24ff9a1cde88.2ibwag4iedx494ft.rcgu.o" "/home/michael/Documents/tinyvm-rs/target/debug/deps/tinyvm-8eca24ff9a1cde88.2jdt9fes53g5mxlp.rcgu.o" "/home/michael/Documents/tinyvm-rs/target/debug/deps/tinyvm-8eca24ff9a1cde88.2kv4bwega1wfr8z6.rcgu.o" "/home/michael/Documents/tinyvm-rs/target/debug/deps/tinyvm-8eca24ff9a1cde88.2lja418hz58xlryz.rcgu.o" "/home/michael/Documents/tinyvm-rs/target/debug/deps/tinyvm-8eca24ff9a1cde88.2o0foimqe73p8ujt.rcgu.o" "/home/michael/Documents/tinyvm-rs/target/debug/deps/tinyvm-8eca24ff9a1cde88.2ouuhyii88vg8tqs.rcgu.o" "/home/michael/Documents/tinyvm-rs/target/debug/deps/tinyvm-8eca24ff9a1cde88.2tnynvvdxge4sv9a.rcgu.o" "/home/michael/Documents/tinyvm-rs/target/debug/deps/tinyvm-8eca24ff9a1cde88.2u1hzhj3v0d8kn4s.rcgu.o" "/home/michael/Documents/tinyvm-rs/target/debug/deps/tinyvm-8eca24ff9a1cde88.2v1ii2legejcp3ir.rcgu.o" "/home/michael/Documents/tinyvm-rs/target/debug/deps/tinyvm-8eca24ff9a1cde88.2vkkoofkb7zs04v1.rcgu.o" "/home/michael/Documents/tinyvm-rs/target/debug/deps/tinyvm-8eca24ff9a1cde88.2w5mgql1gpr1f9uz.rcgu.o" "/home/michael/Documents/tinyvm-rs/target/debug/deps/tinyvm-8eca24ff9a1cde88.2wdyioq7lxh9uxu7.rcgu.o" "/home/michael/Documents/tinyvm-rs/target/debug/deps/tinyvm-8eca24ff9a1cde88.2wokgurbjsmgz12r.rcgu.o" "/home/michael/Documents/tinyvm-rs/target/debug/deps/tinyvm-8eca24ff9a1cde88.2wwcrmvusj07mx2n.rcgu.o" "/home/michael/Documents/tinyvm-rs/target/debug/deps/tinyvm-8eca24ff9a1cde88.310mxv7piqfbf4tr.rcgu.o" "/home/michael/Documents/tinyvm-rs/target/debug/deps/tinyvm-8eca24ff9a1cde88.3352aele91geo33m.rcgu.o" "/home/michael/Documents/tinyvm-rs/target/debug/deps/tinyvm-8eca24ff9a1cde88.36f4wrjtv0x5y00b.rcgu.o" "/home/michael/Documents/tinyvm-rs/target/debug/deps/tinyvm-8eca24ff9a1cde88.38f6o2m900r5q63j.rcgu.o" "/home/michael/Documents/tinyvm-rs/target/debug/deps/tinyvm-8eca24ff9a1cde88.3b67z5wg30f9te4l.rcgu.o" "/home/michael/Documents/tinyvm-rs/target/debug/deps/tinyvm-8eca24ff9a1cde88.3gyajmii4500y81t.rcgu.o" "/home/michael/Documents/tinyvm-rs/target/debug/deps/tinyvm-8eca24ff9a1cde88.3ovwslgcz03sp0ov.rcgu.o" "/home/michael/Documents/tinyvm-rs/target/debug/deps/tinyvm-8eca24ff9a1cde88.3vwhwp967j90qfpp.rcgu.o" "/home/michael/Documents/tinyvm-rs/target/debug/deps/tinyvm-8eca24ff9a1cde88.41ox17npnikcezii.rcgu.o" "/home/michael/Documents/tinyvm-rs/target/debug/deps/tinyvm-8eca24ff9a1cde88.4472ut4qn508rg19.rcgu.o" "/home/michael/Documents/tinyvm-rs/target/debug/deps/tinyvm-8eca24ff9a1cde88.4bbcvjauqmyr7tjc.rcgu.o" "/home/michael/Documents/tinyvm-rs/target/debug/deps/tinyvm-8eca24ff9a1cde88.4c9lrc1xbvaru030.rcgu.o" "/home/michael/Documents/tinyvm-rs/target/debug/deps/tinyvm-8eca24ff9a1cde88.4fzwdkjhjtwv5uik.rcgu.o" "/home/michael/Documents/tinyvm-rs/target/debug/deps/tinyvm-8eca24ff9a1cde88.4gy2dy14zw2o60sh.rcgu.o" "/home/michael/Documents/tinyvm-rs/target/debug/deps/tinyvm-8eca24ff9a1cde88.4i8qxpi0pmwn8d2e.rcgu.o" "/home/michael/Documents/tinyvm-rs/target/debug/deps/tinyvm-8eca24ff9a1cde88.4isstj7ytb9d9yep.rcgu.o" "/home/michael/Documents/tinyvm-rs/target/debug/deps/tinyvm-8eca24ff9a1cde88.4isz4o5d1flv8pme.rcgu.o" "/home/michael/Documents/tinyvm-rs/target/debug/deps/tinyvm-8eca24ff9a1cde88.4lnnaom9zd4u3xmv.rcgu.o" "/home/michael/Documents/tinyvm-rs/target/debug/deps/tinyvm-8eca24ff9a1cde88.4mgvbbhn4jewmy60.rcgu.o" "/home/michael/Documents/tinyvm-rs/target/debug/deps/tinyvm-8eca24ff9a1cde88.4q7wf9d53jp9j6y6.rcgu.o" "/home/michael/Documents/tinyvm-rs/target/debug/deps/tinyvm-8eca24ff9a1cde88.4qimnegzmsif2zbr.rcgu.o" "/home/michael/Documents/tinyvm-rs/target/debug/deps/tinyvm-8eca24ff9a1cde88.4scm7492lh4yspgt.rcgu.o" "/home/michael/Documents/tinyvm-rs/target/debug/deps/tinyvm-8eca24ff9a1cde88.4ten9b8okg10ap4i.rcgu.o" "/home/michael/Documents/tinyvm-rs/target/debug/deps/tinyvm-8eca24ff9a1cde88.4vrj7dhlet4j6oe.rcgu.o" "/home/michael/Documents/tinyvm-rs/target/debug/deps/tinyvm-8eca24ff9a1cde88.4wtf4i2ggbrvqt63.rcgu.o" "/home/michael/Documents/tinyvm-rs/target/debug/deps/tinyvm-8eca24ff9a1cde88.4zsqxnhj8yusiplh.rcgu.o" "/home/michael/Documents/tinyvm-rs/target/debug/deps/tinyvm-8eca24ff9a1cde88.50o8i1bmvqwd5eg7.rcgu.o" "/home/michael/Documents/tinyvm-rs/target/debug/deps/tinyvm-8eca24ff9a1cde88.50urmck1r52hucuw.rcgu.o" "/home/michael/Documents/tinyvm-rs/target/debug/deps/tinyvm-8eca24ff9a1cde88.51w3uc6agh3gynn3.rcgu.o" "/home/michael/Documents/tinyvm-rs/target/debug/deps/tinyvm-8eca24ff9a1cde88.55o6ad6nlq4o2zyt.rcgu.o" "/home/michael/Documents/tinyvm-rs/target/debug/deps/tinyvm-8eca24ff9a1cde88.57gih8p2bu1jbo0l.rcgu.o" "/home/michael/Documents/tinyvm-rs/target/debug/deps/tinyvm-8eca24ff9a1cde88.57rpuf5wpgkfmf1z.rcgu.o" "/home/michael/Documents/tinyvm-rs/target/debug/deps/tinyvm-8eca24ff9a1cde88.5920w55mlosqy9aj.rcgu.o" "/home/michael/Documents/tinyvm-rs/target/debug/deps/tinyvm-8eca24ff9a1cde88.5c1ra5cheein740g.rcgu.o" "/home/michael/Documents/tinyvm-rs/target/debug/deps/tinyvm-8eca24ff9a1cde88.5cuuq0m7tzehyrti.rcgu.o" "/home/michael/Documents/tinyvm-rs/target/debug/deps/tinyvm-8eca24ff9a1cde88.5e85z18y46lhofte.rcgu.o" "/home/michael/Documents/tinyvm-rs/target/debug/deps/tinyvm-8eca24ff9a1cde88.6yu7c01lw47met2.rcgu.o" "/home/michael/Documents/tinyvm-rs/target/debug/deps/tinyvm-8eca24ff9a1cde88.cn69np51jgriev2.rcgu.o" "/home/michael/Documents/tinyvm-rs/target/debug/deps/tinyvm-8eca24ff9a1cde88.d224rq9cs4mbv0q.rcgu.o" "/home/michael/Documents/tinyvm-rs/target/debug/deps/tinyvm-8eca24ff9a1cde88.e0vaqgnhc25c4ox.rcgu.o" "/home/michael/Documents/tinyvm-rs/target/debug/deps/tinyvm-8eca24ff9a1cde88.edm0ce3nfzegp4d.rcgu.o" "/home/michael/Documents/tinyvm-rs/target/debug/deps/tinyvm-8eca24ff9a1cde88.elxjhifv4wlzkc2.rcgu.o" "/home/michael/Documents/tinyvm-rs/target/debug/deps/tinyvm-8eca24ff9a1cde88.ifqyaukx6gnbb0a.rcgu.o" "/home/michael/Documents/tinyvm-rs/target/debug/deps/tinyvm-8eca24ff9a1cde88.kr8s9rcy6ux2d02.rcgu.o" "/home/michael/Documents/tinyvm-rs/target/debug/deps/tinyvm-8eca24ff9a1cde88.ley637x8c2etn66.rcgu.o" "/home/michael/Documents/tinyvm-rs/target/debug/deps/tinyvm-8eca24ff9a1cde88.njyqsm0frvb1j4d.rcgu.o" "/home/michael/Documents/tinyvm-rs/target/debug/deps/tinyvm-8eca24ff9a1cde88.r9ttxk3s5kacz9k.rcgu.o" "/home/michael/Documents/tinyvm-rs/target/debug/deps/tinyvm-8eca24ff9a1cde88.xrorvssabbgfjqz.rcgu.o" "-o" "/home/michael/Documents/tinyvm-rs/target/debug/deps/tinyvm-8eca24ff9a1cde88" "/home/michael/Documents/tinyvm-rs/target/debug/deps/tinyvm-8eca24ff9a1cde88.1iplfu0pt8fy07e4.rcgu.o" "-Wl,--gc-sections" "-pie" "-Wl,-zrelro" "-Wl,-znow" "-nodefaultlibs" "-L" "/home/michael/Documents/tinyvm-rs/target/debug/deps" "-L" "/home/michael/Documents/tinyvm-rs/target/debug/build/tinyvm-3f1a2766f78b5580/out" "-L" "/home/michael/.rustup/toolchains/nightly-x86_64-unknown-linux-gnu/lib/rustlib/x86_64-unknown-linux-gnu/lib" "-Wl,-Bstatic" "-Wl,--whole-archive" "-ltvm" "-Wl,--no-whole-archive" "/home/michael/.rustup/toolchains/nightly-x86_64-unknown-linux-gnu/lib/rustlib/x86_64-unknown-linux-gnu/lib/libtest-a39a3e9a77b17f55.rlib" "/home/michael/.rustup/toolchains/nightly-x86_64-unknown-linux-gnu/lib/rustlib/x86_64-unknown-linux-gnu/lib/libterm-97a69cd310ff0925.rlib" "/home/michael/.rustup/toolchains/nightly-x86_64-unknown-linux-gnu/lib/rustlib/x86_64-unknown-linux-gnu/lib/libgetopts-66a42b1d94e3e6f9.rlib" "/home/michael/.rustup/toolchains/nightly-x86_64-unknown-linux-gnu/lib/rustlib/x86_64-unknown-linux-gnu/lib/libunicode_width-dd7761d848144e0d.rlib" "/home/michael/.rustup/toolchains/nightly-x86_64-unknown-linux-gnu/lib/rustlib/x86_64-unknown-linux-gnu/lib/librustc_std_workspace_std-f722acdb78755ba0.rlib" "/home/michael/Documents/tinyvm-rs/target/debug/deps/libtempfile-b08849d192e5c2e1.rlib"
"/home/michael/Documents/tinyvm-rs/target/debug/deps/librand-c85ceffb304c7385.rlib" "/home/michael/Documents/tinyvm-rs/target/debug/deps/librand_chacha-4e4839e3036afe89.rlib" "/home/michael/Documents/tinyvm-rs/target/debug/deps/libc2_chacha-7555b62a53de8bdf.rlib" "/home/michael/Documents/tinyvm-rs/target/debug/deps/libppv_lite86-0097c0f425957d6e.rlib" "/home/michael/Documents/tinyvm-rs/target/debug/deps/librand_core-de2208c863d15e9b.rlib" "/home/michael/Documents/tinyvm-rs/target/debug/deps/libgetrandom-c696cd809d660e17.rlib" "/home/michael/Documents/tinyvm-rs/target/debug/deps/liblibc-d52d0b97a33a5f02.rlib" "/home/michael/Documents/tinyvm-rs/target/debug/deps/libremove_dir_all-4035fb46dbd6fb92.rlib" "/home/michael/Documents/tinyvm-rs/target/debug/deps/libcfg_if-6adeb646d05b676c.rlib" "-Wl,--start-group" "/home/michael/.rustup/toolchains/nightly-x86_64-unknown-linux-gnu/lib/rustlib/x86_64-unknown-linux-gnu/lib/libstd-974c3c08f6def4b3.rlib" "/home/michael/.rustup/toolchains/nightly-x86_64-unknown-linux-gnu/lib/rustlib/x86_64-unknown-linux-gnu/lib/libpanic_unwind-eb49676f33a2c8a6.rlib" "/home/michael/.rustup/toolchains/nightly-x86_64-unknown-linux-gnu/lib/rustlib/x86_64-unknown-linux-gnu/lib/libhashbrown-7ae0446feecc60f2.rlib" "/home/michael/.rustup/toolchains/nightly-x86_64-unknown-linux-gnu/lib/rustlib/x86_64-unknown-linux-gnu/lib/librustc_std_workspace_alloc-2de299b65d7f5721.rlib" "/home/michael/.rustup/toolchains/nightly-x86_64-unknown-linux-gnu/lib/rustlib/x86_64-unknown-linux-gnu/lib/libbacktrace-64514775bc06309a.rlib" "/home/michael/.rustup/toolchains/nightly-x86_64-unknown-linux-gnu/lib/rustlib/x86_64-unknown-linux-gnu/lib/libbacktrace_sys-1ed8aa185c63b9a5.rlib" "/home/michael/.rustup/toolchains/nightly-x86_64-unknown-linux-gnu/lib/rustlib/x86_64-unknown-linux-gnu/lib/librustc_demangle-a839df87f563fba5.rlib" "/home/michael/.rustup/toolchains/nightly-x86_64-unknown-linux-gnu/lib/rustlib/x86_64-unknown-linux-gnu/lib/libunwind-8e726bdc2018d836.rlib" "/home/michael/.rustup/toolchains/nightly-x86_64-unknown-linux-gnu/lib/rustlib/x86_64-unknown-linux-gnu/lib/libcfg_if-5285f42cbadf207d.rlib" "/home/michael/.rustup/toolchains/nightly-x86_64-unknown-linux-gnu/lib/rustlib/x86_64-unknown-linux-gnu/lib/liblibc-b0362d20f8aa58fa.rlib" "/home/michael/.rustup/toolchains/nightly-x86_64-unknown-linux-gnu/lib/rustlib/x86_64-unknown-linux-gnu/lib/liballoc-f3dd7051708453a4.rlib" "/home/michael/.rustup/toolchains/nightly-x86_64-unknown-linux-gnu/lib/rustlib/x86_64-unknown-linux-gnu/lib/librustc_std_workspace_core-83744846c43307ce.rlib" "/home/michael/.rustup/toolchains/nightly-x86_64-unknown-linux-gnu/lib/rustlib/x86_64-unknown-linux-gnu/lib/libcore-d5565a3a0f4cfe21.rlib" "-Wl,--end-group" "/home/michael/.rustup/toolchains/nightly-x86_64-unknown-linux-gnu/lib/rustlib/x86_64-unknown-linux-gnu/lib/libcompiler_builtins-ea790e85415e3bbf.rlib" "-Wl,-Bdynamic" "-lutil" "-lutil" "-ldl" "-lrt" "-lpthread" "-lgcc_s" "-lc" "-lm" "-lrt" "-lpthread" "-lutil" "-lutil" "-fuse-ld=lld"
= note: ld.lld: error: duplicate symbol: tvm_preprocess
>>> defined at preprocessing.rs:13 (src/preprocessing.rs:13)
>>> /home/michael/Documents/tinyvm-rs/target/debug/deps/tinyvm-8eca24ff9a1cde88.4mgvbbhn4jewmy60.rcgu.o:(tvm_preprocess)
>>> defined at tvm_preprocessor.c:135 (vendor/tinyvm/libtvm/tvm_preprocessor.c:135)
>>> tvm_preprocessor.o:(.text.tvm_preprocess+0x0) in archive /home/michael/Documents/tinyvm-rs/target/debug/build/tinyvm-3f1a2766f78b5580/out/libtvm.a
clang: error: linker command failed with exit code 1 (use -v to see invocation)
error: aborting due to previous error
error: could not compile `tinyvm`.
To learn more, run the command again with --verbose.
Opa, o vinculador reclama que e
preprocessing.rs
, e tvm_preprocessor.c
define uma função tvm_preprocess()
. Parece que esquecemos de remover tvm_preprocessor.c
da montagem ...
diff --git a/build.rs b/build.rs
index 0ed012c..42b8fa0 100644
--- a/build.rs
+++ b/build.rs
@@ -14,6 +14,7 @@ fn main() {
.file(src.join("tvm_memory.c"))
.file(src.join("tvm_parser.c"))
.file(src.join("tvm_program.c"))
- .file(src.join("tvm_preprocessor.c"))
.file(src.join("tvm.c"))
.include(&include)
.compile("tvm");
(END)
Vamos tentar de novo.
cargo run --example tvmi -- vendor/tinyvm/programs/tinyvm/fact.vm
Finished dev [unoptimized + debuginfo] target(s) in 0.02s
Running `target/debug/examples/tvmi vendor/tinyvm/programs/tinyvm/fact.vm`
1
2
6
24
120
720
5040
40320
362880
3628800
Muito melhor!
Lembra daquele último exemplo, onde você
tvmi
caiu, obtendo três níveis de profundidade de código? Como um bom efeito colateral, após portar seu código para o Rust, as camadas aninhadas simplesmente funcionam .
Nota Você
também deve ter notado que a funçãopreprocess()
não usa nenhuma das funções da tabela hash detvm_htab.h
. Depois de portar o módulo para Rust, apenas usamos os tipos Rust diretamente.
Essa é a beleza desse processo. Depois de portar algo para o Rust, você pode aplicá-lo para usar os tipos / funções diretamente - e se beneficiar instantaneamente do tratamento de erros e da ergonomia.
Conclusão
Se você ainda está lendo este artigo, parabéns, acabamos de portar dois módulos do
tinyvm
para o Rust.
Infelizmente, este artigo já é bastante longo. Mas espero que agora você tenha o quadro geral.
- Navegue pelos cabeçalhos do aplicativo e encontre uma função / módulo simples
- Escreva alguns testes para entender como uma função existente deve funcionar
- Escreva funções equivalentes em Rust e certifique-se de que passam nos mesmos testes
- Crie um thin shim que exporte uma função Rust com a mesma interface C, lembrando-se de remover a função / módulo original do assembly para que o vinculador use o código Rust em vez de C
- Vá para a etapa 1
A melhor coisa sobre esse método é que você melhora gradualmente a base de código, o aplicativo permanece funcional e você não reescreve tudo do início ao fim.
É como trocar uma roda na hora.
A maneira preferida de transferir um aplicativo de C para Rust
Veja também: