Concurrencia extensible con los traits Sync y Send

Curiosamente, el lenguaje Rust tiene muy pocas características de concurrencia. Casi todas las características de concurrencia de las que hemos hablado hasta ahora en este capítulo han sido parte de la biblioteca estándar, no del lenguaje. Sus opciones para manejar la concurrencia no se limitan al lenguaje o a la biblioteca estándar; puede escribir sus propias características de concurrencia o usar las escritas por otros.

Sin embargo, dos conceptos de concurrencia están integrados en el lenguaje: los traits Sync y Send de std::marker.

Permitiendo la transferencia de Ownership entre hilos con Send

El trait Send indica que la propiedad de un valor se puede transferir entre hilos. Casi todos los tipos son Send, con algunas excepciones notables, como Rc<T>, que no es Send porque si clonara un valor de Rc<T> y tratara de transferir la propiedad del clon a otro hilo, ambos hilos podrían actualizar el recuento de referencias al mismo tiempo. Por esta razón, Rc<T> está implementado para su uso en situaciones de un solo hilo donde no desea pagar la penalización de rendimiento segura para subprocesos.

Por lo tanto, el sistema de tipos y los límites de los traits de Rust garantizan que nunca pueda enviar accidentalmente un valor Rc<T> a través de hilos de forma insegura. Cuando intentamos hacer esto en el Listado 16-14, obtuvimos el error the trait Send is not implemented for Rc<Mutex<i32>>. Cuando cambiamos a Arc<T>, que es Send, el código se compiló.

Cualquier tipo compuesto enteramente de tipos Send se marca automáticamente como Send también. Casi todos los tipos primitivos son Send, aparte de los punteros sin procesar, que discutiremos en el Capítulo 19.

Permitiendo el acceso desde múltiples hilos con Sync

El trait Sync indica que es seguro que el tipo que implementa Sync se referencie desde múltiples hilos. En otras palabras, cualquier tipo T es Sync si &T (una referencia inmutable a T) es Send, lo que significa que la referencia se puede enviar de forma segura a otro hilo. De manera similar a Send, los tipos primitivos son Sync, y los tipos compuestos enteramente de tipos que son Sync también son Sync.

El smart pointer Rc<T> tampoco es Sync por las mismas razones por las que no es Send. El tipo RefCell<T> (del que hablamos en el Capítulo 15) y la familia de tipos relacionados Cell<T> no son Sync. La implementación de la comprobación de préstamos que hace RefCell<T> en tiempo de ejecución no es segura para subprocesos. El smart pointer Mutex<T> es Sync y se puede usar para compartir el acceso con múltiples hilos como viste en la sección “Compartir un Mutex<T> entre múltiples hilos”.

Implementar Send y Sync manualmente es inseguro

Debido a que los tipos que están compuestos de los traits Send y Sync se automatizan también Send y Sync, no tenemos que implementar esos traits manualmente. Como marcadores de traits, ni siquiera tienen ningún método para implementar. Son útiles para hacer cumplir invariantes relacionados con la concurrencia.

Implementar manualmente estos traits implica implementar código inseguro de Rust. Hablaremos sobre el uso de código inseguro de Rust en el Capítulo 19; por ahora, la información importante es que la construcción de nuevos tipos concurrentes que no están compuestos de partes Send y Sync requiere un pensamiento cuidadoso para mantener las garantías de seguridad. “The Rustonomicon” tiene más información sobre estas garantías y cómo mantenerlas.

Resumen

No es la última vez que verás la concurrencia en este libro: el proyecto del Capítulo 20 usará los conceptos de este capítulo en una situación más realista que los ejemplos más pequeños que se discuten aquí.

Como se mencionó anteriormente, debido a que muy poco de cómo Rust maneja la concurrencia es parte del lenguaje, muchas soluciones de concurrencia se implementan como cajones. Estos evolucionan más rápido que la biblioteca estándar, así que asegúrese de buscar en línea las cajas actuales de última generación para usar en situaciones de múltiples subprocesos.

La biblioteca estándar de Rust proporciona canales para el paso de mensajes y tipos de smart pointer, como Mutex<T> y Arc<T>, que son seguros de usar en contextos concurrentes. El sistema de tipos y el borrow checker garantizan que el código que usa estas soluciones no terminará con carreras de datos o referencias no válidas. Una vez que haya compilado su código, puede estar seguro de que se ejecutará felizmente en múltiples hilos sin los tipos de errores difíciles de rastrear comunes en otros lenguajes. La programación concurrente ya no es un concepto del que tener miedo: ¡adelante y haga que sus programas sean concurrentes, sin miedo!

A continuación, hablaremos sobre las formas idiomáticas de modelar problemas y estructurar soluciones a medida que sus programas Rust se vuelven más grandes. Además, discutiremos cómo los ideales de Rust se relacionan con los que puede estar familiarizado con la programación orientada a objetos.