BookinglyTech News
Software

El puntero crudo de Mojo: seguro y peligroso al mismo tiempo

Mojo introduce un puntero que, según su uso, puede ser totalmente seguro o totalmente peligroso, gracias a su modelo de propiedad y vida útil.

3 min de lecturaLobsters0 vistas

Mojo, el lenguaje que busca la seguridad por defecto, presenta un tipo de puntero que cambia de carácter según la operación que se realice sobre él. El mismo puntero puede ser completamente seguro cuando apunta a una variable existente, o bien convertirse en un riesgo de undefined behaviour cuando se manipula de forma no controlada.

El modelo de Mojo se basa en tres pilares: ownership único, destrucción ASAP y orígenes. El ownership único garantiza que un valor tenga un solo propietario, mientras que la destrucción ASAP elimina los valores tan pronto como sea seguro, evitando que queden huellas en la memoria al final del ámbito. Los orígenes, por su parte, se adjuntan a las referencias y permiten dos verificaciones: exclusividad de argumentos y duración del valor referenciado.

En la práctica, los punteros de Mojo son no nulos y llevan información de origen. Un ejemplo simple:

var some_value = "test value"
var v_ptr: Pointer[String, origin_of(some_value)] = Pointer(to=some_value)
print(v_ptr[])

El compilador infiere el tipo y garantiza que v_ptr apunte a un valor inicializado, no nulo, y que some_value permanezca vivo mientras la referencia exista. Si intentamos pasar dos punteros mutables al mismo origen a una función, el compilador bloquea la compilación:

func some_operation[o: MutOrigin](ptr1: Pointer[String, o], ptr2: Pointer[String, o]): pass

El error muestra que se están aliasando valores mutables con el mismo origen.

La nulidad se modela con NoneType, y si se necesita un puntero que pueda ser nulo, se envuelve en un Optional:

var nullable: Optional[Pointer[SomeType, SomeOrigin]] = some_operation_that_returns_nullable_pointer()

Esto evita los desreferenciaciones de puntero nulo y sus consecuencias.

La parte interesante es que la seguridad depende de la operación. Todas las funciones que son potencialmente inseguras llevan la palabra unsafe en su firma. Por ejemplo, acceder a un elemento fuera del rango con unsafe_offset compila, pero produce undefined behaviour:

print(v_ptr[unsafe_offset=1])

El puntero sigue siendo el mismo, pero la operación rompe la seguridad del sistema.

Cuando se reserva memoria con alloc, el puntero resultante tiene origen MutUntrackedOrigin, lo que indica que el compilador no puede rastrear su vida útil. El programador debe gestionar manualmente la liberación:

var allocation = alloc(layout)
var basic_ptr: Pointer[BasicType, MutUntrackedOrigin] = allocation^.unsafe_leak()
print(basic_ptr[])

El método unsafe_leak descarta las garantías de seguridad del manejador de asignación, dejando la responsabilidad de liberar el bloque a la aplicación.

En resumen, Mojo ofrece un puntero con doble filo: seguro cuando se usa dentro del modelo de vida útil del lenguaje, pero con la capacidad de operar como un puntero crudo cuando el programador decide ignorar las restricciones. Esta flexibilidad puede ser útil en interacciones FFI o cuando se necesita un control de memoria de bajo nivel, pero también exige una disciplina estricta para evitar errores de memoria.

Para los administradores y arquitectos que contemplan migrar a Mojo o integrar código en él, es esencial comprender cómo los orígenes y la destrucción ASAP afectan la gestión de memoria. La elección de usar unsafe debe ser consciente y justificada, ya que cualquier descuido puede introducir vulnerabilidades de estabilidad y seguridad en la aplicación.