¿Conviene reescribir Fortran en Rust o envolverlo?
Cuándo reescribir Fortran en Rust, cuándo envolver un núcleo probado y cómo comparar paridad, mantenimiento y costes de hardware.

Un núcleo de Fortran que lleva veinte años ofreciendo resultados fiables no se convierte en código malo porque la aplicación que lo rodea se haya vuelto incómoda. Si el núcleo tiene una interfaz estrecha, compilaciones reproducibles y una persona que aún sabe diagnosticarlo, envolverlo detrás de una interfaz moderna suele ser la decisión más barata y segura. El lenguaje de origen, por sí solo, no justifica reescribir unas matemáticas que funcionan.
La decisión cambia cuando el núcleo antiguo controla el despliegue, impide elegir hardware, oculta estado mutable o exige conocimientos que la organización ya no puede aportar. Entonces conservarlo tiene un coste recurrente. Llevarlo a Rust puede costar menos que otro ciclo de compiladores especiales, servidores obsoletos, entregas manuales e incidentes que solo entiende un ingeniero ya jubilado. Lo difícil es comparar esos costes sin tratar la edad como un defecto ni confundir la corrección pasada con una garantía de funcionamiento futuro.
Separe el valor del algoritmo del coste de su contenedor
Un algoritmo probado y su implementación en Fortran son activos relacionados, pero no son el mismo activo. Puede valer la pena conservar las ecuaciones, los coeficientes, las reglas de convergencia y el comportamiento aceptado en los límites, aunque el sistema de compilación y las premisas de ejecución deban desaparecer.
Los equipos suelen llamar «probado» a un núcleo cuando quieren decir que producción ha dependido de él durante años. Ese historial importa, pero solo responde a una pregunta: ¿ha generado el sistema completo resultados aceptables para las entradas que recibió de verdad? No demuestra que el código sea portable, carezca de comportamiento indefinido, sea seguro con concurrencia o resulte comprensible cuando se marche su responsable actual. Una vida larga puede incluso esconder dependencias porque nadie ha compilado el núcleo recientemente en una máquina limpia.
Anote por qué merece conservarse el núcleo antes de decidir qué hacer con él. El inventario útil es concreto:
- El modelo matemático y la versión que acepta el negocio
- Los rangos de entrada observados en producción, incluidos los casos no válidos y degenerados
- La precisión exigida, el comportamiento del redondeo y las tolerancias de convergencia
- Los límites de tiempo y memoria que afectan a un lote o una petición reales
- Las opciones del compilador, bibliotecas enlazadas, archivos de datos y orden de inicialización
Este inventario deja clara una distinción importante. La paridad numérica significa que la nueva ejecución se mantiene dentro de una tolerancia acordada. La paridad de comportamiento también incluye errores, avisos, número de iteraciones, orden de salida, tratamiento de NaN, tiempos de espera y efectos secundarios. Un port puede cumplir la primera definición y romper la aplicación según la segunda. Un envoltorio puede conservar ambas, pero solo si su interfaz no cambia la representación ni el ciclo de vida de forma silenciosa.
La edad del código fuente debe quedar cerca del final del registro de decisión. Las obligaciones observables van al principio. Si nadie sabe expresarlas, ni el envoltorio ni la reescritura están preparados. Primero hay que recuperar el contrato a partir del código y de las pruebas de producción.
Una interfaz estrecha y estable favorece el envoltorio
Envuelva el núcleo cuando sus consumidores puedan describirlo como un pequeño grupo de operaciones deterministas con entradas y salidas numéricas normales. Un buen candidato se parece más a una biblioteca que a una aplicación: inicializa tablas inmutables, recibe arrays y opciones escalares, calcula y devuelve resultados junto con un estado.
Cuente los puntos de la interfaz, no las líneas de Fortran. Un solver de 300.000 líneas expuesto mediante seis operaciones estables puede ser más fácil de contener que una rutina de 6.000 líneas que lee archivos globales, modifica bloques COMMON, escribe informes e invoca una interfaz de usuario. El segundo núcleo tiene una superficie de comportamiento mayor aunque su código fuente sea más pequeño.
El envoltorio resulta atractivo cuando se cumplen todas estas condiciones:
- Un compilador mantenido puede reproducir el binario en los servidores previstos
- El núcleo tiene pruebas o casos grabados con resultados de confianza
- Las llamadas no dependen de estado oculto del proceso, o ese estado se puede aislar
- El coste de convertir datos es pequeño frente al tiempo de cálculo
- Las correcciones de seguridad y los diagnósticos llegan a la interfaz sin modificar las matemáticas
El envoltorio debe encargarse de la validación, los límites de memoria, la información de versión, la telemetría y la conversión entre los tipos de la aplicación y los de Fortran. No debe fingir que repara el comportamiento numérico. Mantener clara esta división permite a los ingenieros de aplicación mejorar la operación sin cambiar por accidente el contrato de los resultados.
También sirve una prueba organizativa: ¿puede una persona recién incorporada compilar la biblioteca, ejecutar sus casos y localizar una llamada fallida sin preguntar al autor original? Si puede, el núcleo conservado es una dependencia controlada. Si no, el envoltorio quizá solo oculte un programa huérfano. La documentación no arregla esto por sí sola. La compilación y el diagnóstico deben funcionar en una máquina vacía.
Use la ABI de C como una unión pequeña y aburrida
Fortran y Rust pueden compartir un límite fiable mediante la interfaz binaria de C, siempre que el lado Fortran utilice ISO_C_BINDING en lugar de convenciones de símbolos específicas del compilador. La unión debe exponer tipos numéricos de ancho fijo, longitudes de arrays explícitas, búferes planos y códigos de estado enteros.
Este punto de entrada de Fortran hace visible la disposición de memoria:
module kernel_api
use, intrinsic :: iso_c_binding
implicit none
contains
subroutine evaluate(n, x, scale, y, status) bind(C, name="kernel_evaluate")
integer(c_int), value :: n
real(c_double), intent(in) :: x(n)
real(c_double), value :: scale
real(c_double), intent(out) :: y(n)
integer(c_int), intent(out) :: status
if (n < 1 .or. scale <= 0.0_c_double) then
status = 1_c_int
return
end if
call legacy_evaluate(n, x, scale, y)
status = 0_c_int
end subroutine evaluate
end module kernel_api
El lado Rust mantiene la operación unsafe dentro de un módulo pequeño y presenta al resto de la aplicación una API de slices comprobada:
unsafe extern "C" {
fn kernel_evaluate(
n: i32,
x: *const f64,
scale: f64,
y: *mut f64,
status: *mut i32,
);
}
pub fn evaluate(x: &[f64], scale: f64) -> Result<Vec<f64>, KernelError> {
let n = i32::try_from(x.len()).map_err(|_| KernelError::InputTooLarge)?;
if x.is_empty() || !scale.is_finite() || scale <= 0.0 {
return Err(KernelError::InvalidInput);
}
let mut y = vec![0.0_f64; x.len()];
let mut status = 0_i32;
unsafe {
kernel_evaluate(n, x.as_ptr(), scale, y.as_mut_ptr(), &mut status);
}
match status {
0 => Ok(y),
code => Err(KernelError::Fortran(code)),
}
}
Este código es poco llamativo a propósito. En el límite entre lenguajes, eso es una virtud. Rust Nomicon describe las llamadas a funciones externas como unsafe porque el compilador no puede verificar el contrato del otro lenguaje. Mantenga el bloque unsafe lo bastante pequeño como para revisarlo y haga visibles todas las precondiciones antes de la llamada.
El manual de interoperabilidad de GNU Fortran explica cómo se asignan los procedimientos y tipos interoperables mediante BIND(C) e ISO_C_BINDING. Siga ese mecanismo en vez de depender de la decoración de nombres habitual de un compilador. Un símbolo exportado que aparece en una herramienta de inspección binaria demuestra lo que contiene la compilación actual, no ofrece una interfaz estable para el compilador de mañana.
No pase structs de Rust, tipos derivados de Fortran, arrays asignables ni cadenas nativas de un lenguaje por la primera versión de esta unión. Aplánelos. En las matrices, documente las dimensiones, la dimensión principal y el orden de almacenamiento. Para texto, pase un búfer de bytes con una longitud y una regla de codificación explícitas. Las representaciones aburridas evitan ambigüedades costosas.
El estado oculto hace fallar los envoltorios
Un envoltorio falla cuando presenta un programa con estado como si fuera una función pura, pero no controla dicho estado. Las variables SAVE, los bloques COMMON, los arrays de trabajo en caché, las variables de entorno, los números de unidad, los archivos del directorio de trabajo y el modo de coma flotante pueden cambiar el resultado de una llamada aparentemente idéntica.
La concurrencia suele revelar primero el engaño. Dos peticiones web entran al mismo tiempo en el envoltorio, ambas modifican el mismo espacio de trabajo guardado y una respuesta contiene valores derivados de la entrada de la otra. Un mutex puede recuperar la corrección, pero también serializa el rendimiento. El aislamiento mediante procesos puede mantener el paralelismo a cambio de memoria y tiempo de inicio. Ninguna opción es incorrecta de por sí; las dos deben aparecer en el modelo de capacidad.
La inicialización es otra rotura habitual. El ejecutable original puede leer coeficientes, establecer una semilla aleatoria o llamar a una rutina de preparación antes de entrar en el recorrido numérico. Una extracción como biblioteca que solo exporta la subrutina final puede devolver números plausibles a partir de un estado sin inicializar o predeterminado. Esos números son más peligrosos que una caída porque la supervisión puede aceptarlos.
Trace el ciclo de vida completo de la llamada:
- Inicie un proceso nuevo y registre cada archivo, valor de entorno y biblioteca que carga.
- Siga la inicialización hasta que pueda ejecutarse la primera operación numérica.
- Llame dos veces al mismo caso y compare todas las salidas y valores de estado.
- Intercale dos casos diferentes y repítalos después en procesos separados.
- Fuerce una entrada no válida, un fallo de asignación cuando sea viable y la falta de convergencia.
Esta secuencia indica si el núcleo puede residir dentro de un servicio con varios hilos, necesita una cola con un solo worker o debe ejecutarse en procesos aislados. También identifica caminos de error que detienen el proceso con STOP, escriben en la salida estándar o dejan resultados parciales. Un envoltorio no puede traducir un error después de que el runtime de Fortran haya terminado el proceso que lo aloja.
La disposición de los arrays merece una comprobación propia. Fortran almacena los arrays por columnas; las bibliotecas de Rust suelen asumir un orden por filas. Una matriz copiada puede parecer correcta en sus dimensiones y, al mismo tiempo, representar su traspuesta o un paso de memoria equivocado. Pruebe una matriz no cuadrada con valores únicos, ya que los casos cuadrados o simétricos ocultan el error. Si las copias de conversión dominan el tiempo de la llamada, cambie la interfaz para que acepte la disposición nativa del núcleo en vez de pagar dos recorridos completos de memoria.
Demuestre la paridad con pruebas parecidas a producción
La paridad necesita una comparación ejecutable, no una revisión del código ni un puñado de salidas doradas. Ejecute el camino antiguo y el candidato con las mismas entradas grabadas, capture todos sus resultados observables y clasifique cada diferencia.
Empiece con tráfico de producción o registros de lotes después de eliminar los datos que el entorno de prueba no deba contener. Las grabaciones incluyen combinaciones que las pruebas sintéticas no encuentran: grupos vacíos junto a grupos grandes, ordenaciones extrañas, indicadores obsoletos, valores cerca de un umbral y reintentos tras un trabajo parcial. Añada casos construidos para límites y tensión numérica, pero no deje que sustituyan a las formas reales de operación.
Un comprobador útil emite un registro como este en cada llamada:
{
"case_id": "settlement-004812",
"operation": "evaluate",
"input_digest": "sha256:...",
"old": {"status": 0, "iterations": 7, "output": [1.25, 3.5]},
"candidate": {"status": 0, "iterations": 7, "output": [1.25, 3.5]},
"comparison": {"max_abs": 0.0, "max_rel": 0.0, "accepted": true}
}
No elija un epsilon global solo porque resulte cómodo. La tolerancia absoluta funciona cerca de cero; la relativa funciona al crecer las magnitudes; algunas salidas exigen límites basados en unidades; los enteros y los estados exactos requieren igualdad exacta. NaN necesita una regla explícita porque la igualdad normal lo trata de forma distinta a los valores finitos. El cero con signo puede importar cuando una operación posterior examina ese signo.
Compare los fallos con el mismo cuidado que los cálculos correctos. Si el camino antiguo informa de falta de convergencia después de 40 iteraciones y el nuevo devuelve la última estimación como éxito, los arrays pueden estar cerca aunque el contrato haya cambiado. Si el orden de salida no está especificado en el código fuente, pero el código posterior ha dependido de él durante años, el comportamiento de producción ha convertido ese orden en una obligación de la migración hasta que cambien los consumidores.
Conserve el comprobador después del lanzamiento. Se convertirá en la defensa para las actualizaciones del compilador, los cambios en opciones de optimización, la sustitución de bibliotecas y el trabajo posterior en Rust. CodeHero utiliza este tipo de comprobador de paridad con tráfico de producción grabado mientras moderniza la arquitectura, porque una batería de pruebas unitarias aprobada no demuestra por sí sola que un sistema heredado mantenga su comportamiento en los límites.
Reescriba cuando se repitan los costes de propiedad
Porte el núcleo cuando mantenerlo cree un impuesto operativo recurrente que el envoltorio no puede eliminar. Los motivos más sólidos afectan a la propiedad y al despliegue, no a los gustos sobre lenguajes.
Un port merece una evaluación seria cuando el compilador Fortran aprobado no admite el entorno operativo de destino, la compilación depende de bibliotecas abandonadas o cada entrega exige una máquina que la organización ya quiere retirar. Lo mismo ocurre cuando el diagnóstico en producción termina en un binario opaco y los fallos no se pueden vincular a entradas, fases o consumo de recursos.
La dotación de personal importa, pero «no contratamos desarrolladores de Fortran» es un argumento demasiado débil por sí solo. Un núcleo estable y envuelto puede exigir poco trabajo en ese lenguaje. Mida la demanda real: con qué frecuencia cambia el algoritmo, cuántos incidentes requieren diagnosticar el código fuente, quién revisa las actualizaciones del compilador y qué sucede cuando no está el responsable actual. Portar solo para coincidir con el lenguaje mayoritario puede gastar mucho dinero sin reducir esas cargas.
Los cambios frecuentes refuerzan el caso. Si el trabajo del producto añade términos al modelo, cambia reglas de convergencia o necesita que el núcleo participe en la cancelación de peticiones y en errores estructurados, cada cambio cruza la unión. El envoltorio acumula políticas que pertenecen al cálculo. Rust ofrece propiedad directa de memoria, tipos de resultado explícitos, paralelismo controlado y herramientas que puede manejar una parte mayor del equipo de aplicación.
Existe además un argumento de seguridad y aislamiento. Si el núcleo consume archivos no fiables, indexa sin comprobación o se ejecuta en el mismo proceso que un servicio expuesto, puede ser obligatorio contenerlo incluso antes del port. Ejecútelo en un proceso worker restringido y con límites de entrada mientras avanza la reescritura. Rust reduce muchos errores de memoria en el código reescrito, pero no demuestra las matemáticas, no evita el agotamiento de recursos ni vuelve seguras las bibliotecas externas inseguras.
Un registro de decisión útil convierte el dolor recurrente en esfuerzo anual de ingeniería y coste de infraestructura. Incluya licencias del compilador, máquinas de compilación especiales, trabajo de entrega, dependencia durante incidentes, capacidad ociosa por la serialización y cambios de plataforma bloqueados. No invente un valor económico para la «modernidad». Asigne valor al trabajo que la organización realiza de verdad.
La configuración del compilador forma parte del contrato
Un comando del compilador es parte del código fuente efectivo del núcleo. Las opciones de optimización, la configuración de coma flotante, la arquitectura de destino, las bibliotecas matemáticas enlazadas e incluso el orden del enlazado pueden cambiar los resultados numéricos o sacar a la luz supuestos que una compilación antigua toleraba por casualidad.
Recupere la compilación exacta antes de evaluar un envoltorio o un port. Guarde la identidad y versión del compilador, todas las opciones de compilación y enlace, las definiciones del preprocesador, las versiones de las bibliotecas y las variables de entorno utilizadas. Capture las órdenes desde la herramienta de build en vez de copiar un comentario de un viejo documento de operaciones. Compile después en un entorno limpio y compare los casos de referencia. Si una compilación limpia ya produce diferencias, el equipo tiene un problema de reproducibilidad antes de empezar la migración.
La optimización agresiva de coma flotante exige especial cuidado. Con opciones matemáticas permisivas, un compilador puede reagrupar operaciones, fusionar una multiplicación y una suma, tratar NaN como imposible o suponer que el signo de cero no tiene significado. Estas transformaciones pueden mejorar la velocidad y cambiar la convergencia cerca de un umbral. La cuestión no es si una opción cumple una norma estricta en abstracto. Pregunte si el contrato de producción permite el cambio y demuestre la respuesta con el comprobador.
Las bibliotecas numéricas externas también pertenecen al registro. Una llamada llamada DGEMM puede conservar la misma interfaz mientras otra implementación de BLAS cambia el orden de reducción, los hilos, la selección de CPU y el rendimiento. Eso suele ser aceptable dentro de una buena tolerancia, pero puede alterar un solver en el límite o sobresuscribir un servicio cuya capa exterior también crea hilos. Registre la implementación de la biblioteca y su configuración de hilos con cada benchmark.
Cree un manifiesto de compilación junto a cada artefacto candidato. Puede ser sencillo:
kernel_version=2026.08
compiler=gfortran
compiler_version=<captured from build>
compile_flags=<captured from build>
blas_implementation=<name and version>
target_cpu=<declared target>
source_digest=<repository revision>
test_corpus=<immutable corpus revision>
Los corchetes angulares indican campos que hay que completar, no permiso para dejar datos sin conocer. Haga que la compilación genere el manifiesto automáticamente y lo exponga en una operación de versión o en el registro de inicio. Cuando un resultado cambie después del despliegue, los operadores podrán saber si cambió el código, el compilador, la biblioteca o el corpus de entrada.
Para el envoltorio, esta disciplina convierte el binario Fortran en un componente gobernado en lugar de un archivo inexplicado que se copia entre servidores. Para el port, proporciona a los ingenieros de Rust el comportamiento real que deben igualar. Las compilaciones de Rust necesitan el mismo trato: fijar las versiones de dependencias, registrar la cadena del compilador, declarar las capacidades de CPU de destino y mantener separadas las opciones de rendimiento y de paridad hasta medir sus efectos.
Evite hacer de la igualdad bit a bit un objetivo universal. Puede ser necesaria para algunos registros regulados o puntos de control binarios, pero también puede congelar una ruta de compilador y hardware de forma indefinida. La mayoría de los sistemas numéricos necesitan tolerancias con sentido científico o financiero, más coincidencia exacta en las decisiones discretas. Documente a qué categoría pertenece cada salida. Un resultado booleano de elegibilidad no puede desviarse por un epsilon aunque proceda de cálculos de coma flotante.
Una última trampa consiste en comparar una compilación antigua y conservadora con un candidato ajustado con todas las optimizaciones disponibles, para después atribuir el resultado al lenguaje. Ejecute una matriz que separe la implementación de la configuración: Fortran de referencia, Fortran optimizado, Rust conservador y Rust optimizado. Esta comparación indica si el ahorro previsto proviene del port, de actualizar el compilador o de aceptar un contrato numérico diferente.
El coste del hardware puede descartar un envoltorio correcto
Un núcleo envuelto puede conservar los resultados a la perfección y aun así convertirse en la opción cara cuando su modelo de ejecución impide utilizar bien el hardware disponible. Mida la carga completa en el hardware que pretende operar, incluidas la conversión de datos, los límites de procesos, el movimiento de memoria y las colas.
No suponga que Rust será más rápido. Los compiladores Fortran maduros generan código numérico excelente y los núcleos consolidados quizá ya llamen a rutinas BLAS o LAPACK ajustadas. Un port literal puede perder vectorización, asignar memoria con mayor frecuencia o cambiar una llamada de biblioteca optimizada por un bucle normal. La elección del lenguaje no elimina el ancho de banda de memoria ni la complejidad algorítmica.
La presión económica suele aparecer en otra parte. Tal vez el núcleo solo se compile para una arquitectura antigua, dependa de un runtime del proveedor que limite el despliegue o serialice el trabajo por su estado global. Puede copiar arrays grandes varias veces para encajar en el límite de un nuevo servicio. Las instancias en la nube quedan infrautilizadas mientras las peticiones esperan detrás de un solo carril de cómputo. Esos costes del sistema se pueden medir y justifican un port aunque una llamada aislada de Fortran siga siendo rápida.
Mida distribuciones representativas, no un único caso favorable. Registre como mínimo el rendimiento, la latencia de cola, el máximo de memoria residente, los bytes copiados en la interfaz y el tiempo de espera ante la zona serializada. Ejecute casos calientes y fríos cuando la inicialización sea relevante. Incluya las versiones y opciones del compilador en el resultado para que otro ingeniero pueda reproducirlo.
Pruebe después las alternativas más baratas a una reescritura. Eliminar una trasposición innecesaria, mantener un conjunto de procesos worker, actualizar el compilador o sustituir una interfaz de archivos por un búfer en memoria puede recuperar capacidad suficiente. Si lo hace, el envoltorio ha ganado otro periodo de servicio. Si el núcleo sigue bloqueando la arquitectura elegida o el camino hacia un acelerador, el port recibe una obligación concreta de rendimiento en vez de una promesa imprecisa de velocidad.
Un port a Rust debe conservar el comportamiento, no la sintaxis
Un port correcto traduce el modelo de cálculo y después le da un diseño que los ingenieros de Rust pueden mantener. La transliteración conserva el flujo de control antiguo, los arrays globales, los valores centinela y las costumbres de indexación, mientras añade peleas con el borrow checker. El resultado es más difícil de revisar que cualquiera de los originales.
Congele la compilación de referencia antes de editar. Asígnele opciones de compilador reproducibles, datos de prueba inmutables y una invocación legible por máquinas. La referencia no tiene que ser bonita; debe seguir disponible hasta que el sustituto haya superado la comparación en producción.
Divida el port por límites matemáticos que pueda observar el comprobador de paridad. Unidades adecuadas son el preprocesamiento, la selección de coeficientes, una iteración del solver, la evaluación de la convergencia y el formato del resultado. Porte una unidad, llámela desde el camino existente cuando sea viable y compare valores intermedios. Así una diferencia queda restringida a una etapa y no obliga a revisar el solver entero.
Haga explícitas las decisiones numéricas en Rust. Indique si los valores son f32 o f64, cómo trata el desbordamiento una conversión de enteros, qué orden de reducción es aceptable y si una multiplicación y suma fusionadas pueden cambiar el redondeo. Conserve primero el algoritmo aceptado. Mejórelo después en un cambio revisado por separado y con pruebas propias, porque combinar la migración de lenguaje con una revisión del modelo destruye la referencia limpia.
Modele los fallos como resultados con tipo y no como valores mágicos, pero mantenga el comportamiento externo anterior hasta que los consumidores adopten de forma deliberada el nuevo contrato. Si el camino Fortran emite una advertencia y una estimación utilizable, la primera entrega de Rust no debe convertir ese caso en un error fatal de manera silenciosa. Una API mejor solo sirve cuando el sistema que la rodea cambia con ella.
Limite el Rust unsafe a los límites con otros lenguajes y a llamadas de bibliotecas auditadas. El Rust puro aún puede hacer panic por un índice, asignar memoria sin un límite práctico o producir NaN mediante operaciones de coma flotante válidas. Establezca límites y devuelva los fallos en la interfaz del servicio. La seguridad de memoria resuelve una clase de fallos, no el contrato operativo.
Mantenga la decisión reversible hasta que decidan las pruebas
El programa más seguro retrasa la elección irreversible mientras mejora las dos opciones. Primero cree una compilación reproducible de Fortran y un comprobador de paridad. Después sitúe el núcleo actual detrás de la interfaz que querría aunque cambiara la implementación. Ese trabajo es necesario para un envoltorio fiable y sigue siendo útil para un port.
Ejecute la versión envuelta bajo una carga representativa. Ya tendrá pruebas sobre el coste de conversión, la concurrencia, el aislamiento de fallos y la propiedad operativa. Si cumple el objetivo del servicio y el equipo puede mantener su compilación, deténgase. Conservar buen Fortran es una decisión de ingeniería, no una falta de ambición.
Si no cumple, la misma interfaz se convierte en el límite del sustituto en Rust y los mismos casos grabados juzgan cada unidad migrada. Despliegue una comparación en sombra cuando el entorno lo permita: ejecute el candidato sin dejar que controle la respuesta, registre diferencias acotadas e inspeccione los casos fuera de tolerancia. Proteja las entradas sensibles y limite el cálculo adicional. La ejecución en sombra es una técnica de prueba, no un permiso para duplicar datos sin cuidado.
Defina los criterios de salida antes de que el port gane inercia. Deben cubrir la paridad aceptada, el rendimiento en los servidores de destino, el comportamiento ante fallos, los diagnósticos operativos y la eliminación de la antigua dependencia de runtime. Defina también la ventana de reversión y las pruebas necesarias para retirar la referencia. Sin esas condiciones, los equipos mantienen dos implementaciones de forma indefinida y pagan ambos costes de propiedad.
La decisión ya se puede expresar sin ideología. Envuelva cuando el cálculo sea estable, la interfaz estrecha y la cadena de herramientas siga siendo operable. Reescriba cuando las restricciones recurrentes de propiedad o hardware cuesten más que un port medido y cuando la organización pueda demostrar la nueva implementación frente a la antigua. Si todavía no puede probar ninguna afirmación, dedique el siguiente día de ingeniería al comprobador, no a traducir un bucle.
Preguntas frecuentes
¿El código Fortran antiguo es inseguro por definición?
No. La edad y el lenguaje no determinan si un núcleo es seguro o correcto. Revise el tratamiento de entradas, el comportamiento de la memoria, el estado mutable, las premisas del compilador y el límite de despliegue antes de juzgarlo.
¿Puede Rust llamar directamente a una biblioteca Fortran?
Sí, mediante una ABI de C compatible que Fortran expone con ISO_C_BINDING y BIND(C). Mantenga la llamada externa dentro de un pequeño módulo unsafe de Rust y pase búferes numéricos simples, longitudes y códigos de estado.
¿Reescribir Fortran en Rust hará que funcione más rápido?
No necesariamente. El código Fortran maduro puede vectorizar bien o llamar a bibliotecas numéricas ajustadas. Mida la carga completa, porque las copias, la serialización, el uso de memoria y las restricciones de despliegue suelen importar más que la sintaxis del bucle.
¿Cómo se comparan resultados de coma flotante tras un port?
Use tolerancias absolutas, relativas o basadas en unidades para cada salida y defina reglas para NaN, infinitos y cero con signo. Compare también estados y convergencia; unos arrays cercanos no demuestran un comportamiento idéntico.
¿Debemos envolver Fortran en el mismo proceso que el servicio web?
Solo si ha comprobado su estado, tratamiento de errores y concurrencia. Un núcleo que llama a STOP, modifica arrays guardados o lee archivos globales del proceso suele necesitar un proceso worker aislado.
¿Cuál es el mayor riesgo de un envoltorio de Fortran?
El estado oculto suele ser la sorpresa más cara. Una firma de función limpia puede esconder el orden de inicialización, bloques COMMON, archivos, semillas aleatorias y espacios de trabajo inseguros con hilos.
¿Qué parte del núcleo conviene portar de una vez?
Porte la unidad matemática más pequeña que pueda observar el comprobador de paridad. Comparar etapas intermedias permite localizar la deriva numérica mucho mejor que sustituir el solver completo en una entrega.
¿Seguimos necesitando un especialista en Fortran después de envolverlo?
Necesita a alguien capaz de compilar, diagnosticar y revisar cambios, pero puede no ser un especialista a tiempo completo. Si solo una persona no disponible puede hacerlo, la dependencia no está bajo control.
¿Cuándo justifica el coste del compilador una reescritura en Rust?
Cuando las licencias, máquinas de build limitadas, trabajo de entrega o plataformas bloqueadas cuestan de forma recurrente más que el port medido y su validación. Incluya facturas reales y horas de ingeniería en la comparación.
¿Podemos mejorar el algoritmo durante la migración a Rust?
Hágalo como un cambio separado después de alcanzar la paridad de comportamiento. Mezclar el port del lenguaje con la revisión del modelo elimina la referencia fiable y dificulta explicar cada diferencia.