En la mayoría de los idiomas, tendrá que volver a escribir toda la biblioteca desde cero y los primeros resultados aparecerán hacia el final del proyecto. Estos puertos son generalmente bastante caros y propensos a errores, y a menudo fallan a la mitad. Joel Spolsky explica esto mucho mejor que yo en su artículo sobre por qué la reelaboración completa de proyectos es una mala idea .
Sin embargo, Rust tiene un toque asesino cuando se trata de este tipo de cosas. Puede invocar código C sin la sobrecarga (es decir, el entorno P / Invoke en C #), y expone funciones que se pueden usar en C como cualquier otra función C. Esto abre la puerta a un enfoque alternativo:
bibliotecas de puertos para Rust una función a la vez.
Nota
El código de este artículo está disponible en GitHub . Siéntase libre de pasar por aquí para pedir prestado código o inspiración.
Si el artículo le resultó útil o notó un error, hágamelo saber en el rastreador de errores del blog .
Empezando
Antes de que pueda hacer algo, debe crear un nuevo proyecto. Tengo una plantilla que instala CI y licencias para generar 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
Nuestro primer desafío real es construir la biblioteca que queremos portar y resolverlo un poco.
En este caso, estamos portando jakogut / tinyvm ,
TinyVM es una máquina virtual pequeña, rápida y ligera escrita en ANSI C.
Para que sea más fácil referenciarlo en el futuro, agreguemos el repositorio como un submódulo a nuestro proyecto.
$ git submodule add https://github.com/jakogut/tinyvm vendor/tinyvm
Ahora veamos el código fuente. Para empezar,
README.md
las instrucciones de montaje.
TinyVM es la máquina virtual más pequeña. Bajo uso de memoria, pequeña cantidad de código y poco código binario.(énfasis agregado)
La compilación se realiza en sistemas similares a UNIX con make y GCC.
Sin dependencias externas, mantenga la biblioteca estándar de C. Las
compilaciones se realizan con make o make rebuild.
Para construir una versión de depuración agregue DEBUG = yes después de make. Para construir un binario con la creación de perfiles habilitada, agregue PROFILE = yes después de make.
Puede comunicarse conmigo en joseph.kogut (at) gmail.com
Bien, echemos un vistazo al directorio
tinyvm
y veamos si la compilación simplemente 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
Realmente me gusta cuando las bibliotecas C se compilan de inmediato sin tener que instalar paquetes aleatorios
*-dev
o jugar con el sistema de compilación.
Desafortunadamente, la biblioteca no contiene ninguna prueba, por lo que no podemos (inmediatamente) asegurarnos de que las funciones individuales se traduzcan correctamente, pero contiene un intérprete de ejemplo que podemos usar para explorar la funcionalidad de alto nivel.
Por lo tanto, sabemos que podemos compilarlo desde la línea de comandos sin muchos problemas. Ahora debemos asegurarnos de que nuestra caja
tinyvm
pueda ensamblar todo mediante programación.
Aquí es donde entran los scripts de compilación. Nuestra estrategia es que Rust crate use el script de compilación
build.rs
y crate cc
para invocar comandos equivalentes a nuestra llamadamake
... Desde allí, podemos conectarnos libtvm
desde Rust como cualquier otra biblioteca nativa.
Deberá agregar la caja
cc
como dependencia.
$ cargo add --build cc
Updating 'https://github.com/rust-lang/crates.io-index' index
Adding cc v1.0.47 to build-dependencies
Y también asegúrese de
build.rs
compilar desde la fuente 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
Si ha mirado la documentación de la cajacc
, es posible que haya notado un métodoBuild::files()
que acepta un iterador de rutas. Nos podríamos programación descubrir todos los archivos*.c
en el interiorvendor/tinyvm/libtvm
, pero ya que está portando la función de código de uno a la vez, es mucho más fácil para eliminar las llamadas individuales.file()
a medida que el puerto.
También necesitamos una forma de decirle a Rust desde qué funciones puede llamar
libtvm
. Esto generalmente se hace escribiendo las definiciones para cada función en un bloque externo , pero afortunadamente existe una herramienta llamada bindgen que puede leer un archivo de encabezado de estilo C y generar definiciones para nosotros.
Generemos enlaces desde
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
Deberá agregar un módulo a nuestra caja
ffi
.
// src/lib.rs
#[allow(non_camel_case_types, non_snake_case)]
pub mod ffi;
Mirando el directorio
src/
en tinyvm
, encontramos el código fuente del intérprete 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;
}
Es increíblemente simple. Lo cual es muy bueno considerando que usaremos este intérprete como uno de nuestros ejemplos.
Por ahora, traduzcamoslo directamente a Rust y pongámoslo en el directorio
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 comprobación, también podemos iniciar una máquina virtual y asegurarnos de que todo funciona.
$ 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
¡Clase!
Fruta madura
Cuando comienza con algo como esto, es tentador sumergirse en las características más importantes y migrarlas primero. Trate de resistir este impulso. Puedes morder fácilmente más de lo que puedes masticar y terminar perdiendo el tiempo o desmoralizándote y haciéndote rendirte.
En cambio, busquemos el más simple.
$ 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 archivo
tvm_htab.
parece prometedor. Estoy bastante seguro de que htab
significa "tabla hash" y la biblioteca estándar de Rust ya contiene una implementación de alta calidad. Deberíamos poder cambiar esto con bastante facilidad.
Echemos un vistazo al archivo de encabezado
tvm_htab.h
y veamos a qué nos enfrentamos.
// 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 bastante fácil de implementar. El único problema es que la definición
tvm_htab_ctx
y tvm_htab_node
están incluidos en el archivo de encabezado, lo que significa que algunos códigos pueden acceder directamente a las partes internas de la tabla hash y no pasar por la interfaz publicada.
Podemos verificar si algo tiene acceso a las partes internas de la tabla hash moviendo temporalmente las definiciones de estructura
tvm_htab.c
y ver si todo aún se 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);
Y luego corre de nuevo
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 todo sigue funcionando, ahora comenzamos la segunda fase; Creamos un conjunto idéntico de funciones que se utilizan bajo el capó
HashMap<K, V>
.
Limitándonos a un stub con el mínimo, obtenemos:
// 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!()
}
También debe declarar el módulo
htab
y volver a exportar sus funciones lib.rs
.
// src/lib.rs
mod htab;
pub use htab::*;
Ahora debemos asegurarnos de que el original
tvm_htab.c
no esté compilado o vinculado a la biblioteca final, de lo contrario, el vinculador se encontrará con un muro de errores de símbolos duplicados.
Muro de errores repitiendo 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`.
La solución es bastante simple.
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 intentar ejecutar el ejemplo
tvmi
nuevamente falla, como es de esperar de un 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.
Al agregar soporte FFI para un nuevo tipo, el lugar más fácil para comenzar es con el constructor y el destructor.
El
código de Info C solo puede acceder a nuestra tabla hash a través de un puntero, por lo que debemos asignar uno de ellos al montón y luego transferir la propiedad de ese objeto asignado al montón al llamador.
// 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);
}
Advertencia ¡
Es importante que las personas que llamanHashTable
solo maten con la funcióntvm_htab_destroy ()
!
Si no lo hacen, y en su lugar intentan llamarfree()
directamente, es casi seguro que tendremos una mala situación. En el mejor de los casos, provocará una gran pérdida de memoria, pero también es muy posible que nuestroBox
en Rust no use el mismo grupo quemalloc()
yfree ()
, lo que significa que la liberación del objeto Rust de C puede dañar la pila y dejarla en un estado roto.
Agregar elementos a un mapa de hash es casi tan 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
.
Se
*_find()
pueden delegar dos funciones directamente a la interna HashMap<CString, Item>
.
El único lugar para tener cuidado es asegurarse de que se devuelva el valor correcto cuando no se puede encontrar el elemento. En este caso, mirando
tvm_htab.c
podemos ver qué tvm_htab_find()
devuelve −1
y qué tvm_htab_find_ref()
devuelve 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(),
}
}
Ahora que hemos implementado la funcionalidad stub, todo debería funcionar de nuevo.
La forma más sencilla de probar esto es ejecutar nuestro ejemplo.
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
Y para verificarlo, podemos ejecutarlo
valgrind
para asegurarnos de que no haya pérdidas de memoria ni nada complicado con los punteros.
$ 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)
¡Éxito!
Implementación de preprocesamiento de datos
La máquina virtual
tinyvm
utiliza una forma simplificada de ensamblador , similar al ensamblador Intel x86 tradicional. El primer paso para analizar el ensamblador de tinyvm es ejecutar un preprocesador que interprete declaraciones %include filename
y declaraciones %define identifier value
.
Este tipo de manipulación de texto es mucho más fácil con los tipos
&str
en Rust, así que echemos un vistazo a la interfaz que nuestra caja necesita 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 variables src
y src_len
puede parecer un poco extraño al principio, pero si escribiera el equivalente en Rust, terminaría con algo como esto:
fn tvm_preprocess(
src: String,
defines: &mut HashTable,
) -> Result<String, PreprocessorError> {
...
}
El código C solo usa los parámetros de salida para reemplazar la cadena
src
en su lugar, porque no puede devolver una nueva línea o un código de error.
Antes de hacer cualquier otra cosa, debe escribir una prueba para
tvm_preprocess()
. De esta forma podemos asegurarnos de que nuestra función Rust sea funcionalmente equivalente a la original.
Interactuamos con el sistema de archivos, por lo que tendremos que sacar la caja del archivo temporal .
$ cargo add --dev tempfile
Updating 'https://github.com/rust-lang/crates.io-index' index
Adding tempfile v3.1.0 to dev-dependencies
También necesitamos una caja
libc
, porque pasaremos líneas libtvm
que puede que necesite liberar.
cargo add libc
Updating 'https://github.com/rust-lang/crates.io-index' index
Adding libc v0.2.66 to dev-dependencies
Mirando el código fuente, podemos ver que la función
tvm_preprocess()
continuará permitiéndolo %include
y %define
siempre que sea no.
Primero, creemos una prueba para asegurarnos de que el preprocesador maneja
%define
. Sabemos que este código ya está funcionando (después de todo tinyvm
, este es el código ), por lo que no debería haber sorpresas.
// 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 líneas es mucho más de lo que normalmente me gusta en las pruebas, pero se necesita una buena cantidad de código adicional para convertir entre líneas C.
También necesitamos verificar, incluido un archivo más.
// 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());
}
}
Nota
Como nota al margen, la prueba se escribió originalmente para anidar todo en tres capas de profundidad (por ejemplo,top_level.vm
incluye nested.vm, que incluye really_nested.vm) para asegurarse de que maneja más de un nivel%include
, pero independientemente de Tal como estaba escrito, la prueba siguió teniendo errores de segmentación.
Luego intenté ejecutar el binario 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
Resulta que el tinyvm original se bloquea por alguna razón cuando tienes varias capasinclude
...
Entonces ahora tenemos algunas pruebas, para que podamos comenzar a implementar
tvm_preprocess()
.
Primero, debe determinar el tipo de error.
// 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),
}
Al observar las funciones process_includes () y process_derives () , parece que escanean una cadena en busca de una directiva específica y luego reemplazan esa cadena con otra cosa (ya sea el contenido del archivo o nada si la línea debe eliminarse).
Necesitamos poder extraer esta lógica en un ayudante y evitar duplicaciones innecesarias.
// 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))
}
Ahora tenemos un ayudante
process_line_starting_with_directive()
, por lo que podemos implementar un analizador %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,
}
})
})
}
Desafortunadamente, el analizador% define es un poco más complejo.
// 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 acceder al texto en nuestra tabla hash, necesitaremos darle al elemento un
Item
par de 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()
}
}
Es una buena idea agregar algunas pruebas más en este 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");
}
}
En este punto, hemos reproducido la mayor parte de la lógica de preprocesamiento, por lo que ahora solo necesitamos una función que continuará expandiendo los operadores
%include
y procesándolos %define
hasta que no haya más.
// 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;
}
}
Por supuesto, esta función
preprocess()
solo está disponible para Rust. Necesitamos crear extern "C" fn
uno que traduzca los argumentos de los tipos C en algo que Rust pueda manejar, y luego se traduzca de nuevo a 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,
}
}
Sugerencia Es
posible que haya notado que nuestra función tvm_preprocess () no tiene ninguna lógica de preprocesamiento y es más como un adaptador para traducir argumentos y valores de retorno, y para asegurar la propagación correcta de errores.
No es casualidad.
El secreto para codificar FFI es escribir lo menos posible y evitar trucos inteligentes . A diferencia de la mayoría de los códigos de Rust, los errores en dichas funciones de interoperabilidad pueden provocar errores en la lógica y la memoria.
La creación de un contenedor delgado alrededor de nuestra funciónpreprocess()
también facilita las cosas: cuando la mayor parte del código base está escrito en Rust, podemos eliminar el contenedor y llamarpreprocess()
directamente.
La función ahora está
tvm_preprocess()
definida y deberíamos estar listos para comenzar.
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.
Vaya, el enlazador se queja de que y
preprocessing.rs
, y tvm_preprocessor.c
define una función tvm_preprocess()
. Parece que nos olvidamos de quitarlo tvm_preprocessor.c
del ensamblaje ...
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)
Intentemoslo de nuevo.
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
¡Mucho mejor!
¿Recuerda el último ejemplo, donde
tvmi
cayó, obteniendo tres niveles de profundidad de código? Como efecto secundario agradable, después de portar su código a Rust, las capas anidadas simplemente funcionan .
Nota También
puede haber notado que la funciónpreprocess()
no utiliza ninguna de las funciones de tabla hash detvm_htab.h
. Después de portar el módulo a Rust, solo usamos los tipos de Rust directamente.
Ésta es la belleza de este proceso. Una vez que haya transferido algo a Rust, puede aplicarlo para usar los tipos / funciones directamente, y beneficiarse instantáneamente del manejo de errores y la ergonomía.
Conclusión
Si todavía está leyendo este artículo, felicitaciones, acabamos de migrar dos módulos de
tinyvm
a Rust.
Desafortunadamente, este artículo ya es bastante extenso. Pero espero que a estas alturas ya tengas el panorama completo.
- Examine los encabezados de la aplicación y encuentre una función / módulo simple
- Escriba algunas pruebas para comprender cómo debería funcionar una función existente
- Escriba funciones equivalentes en Rust y asegúrese de que pasen las mismas pruebas
- Cree una calza delgada que exporte una función Rust con la misma interfaz C, recordando eliminar la función / módulo original del ensamblaje para que el vinculador use el código Rust en lugar de C
- Ir al paso 1
Lo mejor de este método es que mejora gradualmente el código base, la aplicación sigue funcionando y no reescribe todo de principio a fin.
Es como cambiar una rueda sobre la marcha.
La forma preferida de portar una aplicación de C a Rust
Ver también: