Rust es un lenguaje de programación de propósito general y multiparadigma que enfatiza el rendimiento , la seguridad de tipos y la concurrencia . Hace cumplir la seguridad de la memoria , lo que significa que todas las referencias apuntan a una memoria válida, sin un recolector de basura . Para reforzar simultáneamente la seguridad de la memoria y evitar carreras de datos , su "verificador de préstamos" rastrea la vida útil del objeto de todas las referencias en un programa durante la compilación . Rust fue influenciado por ideas de programación funcional , incluida la inmutabilidad , funciones de orden superior y tipos de datos algebraicos . Es popular para la programación de sistemas . [13] [14] [15]
El desarrollador de software Graydon Hoare creó Rust como un proyecto personal mientras trabajaba en Mozilla Research en 2006. Mozilla patrocinó oficialmente el proyecto en 2009. En los años posteriores a la primera versión estable en mayo de 2015, Rust fue adoptado por empresas como Amazon , Discord , Dropbox , Google ( Alfabeto ), Meta y Microsoft . En diciembre de 2022, se convirtió en el primer lenguaje distinto de C y ensamblador admitido en el desarrollo del kernel de Linux .
Rust se ha destacado por su rápida adopción [16] y ha sido estudiado en la investigación de la teoría del lenguaje de programación . [17] [18] [19]
Rust surgió de un proyecto personal iniciado en 2006 por Graydon Hoare, empleado de Mozilla Research. [20] Mozilla comenzó a patrocinar el proyecto en 2009 como parte del desarrollo continuo de un motor de navegador experimental llamado Servo , [21] que fue anunciado oficialmente por Mozilla en 2010. [22] [23] La memoria y el sistema de propiedad de Rust se vieron influenciados mediante gestión de memoria basada en regiones en lenguajes como Cyclone y ML Kit. [5]
Casi al mismo tiempo, el trabajo pasó del compilador inicial escrito en OCaml a un compilador autohospedado basado en LLVM escrito en Rust. El nuevo compilador de Rust se compiló con éxito en 2011. [21] [ se necesita mejor fuente ] En el otoño de 2011, se desarrolló el logotipo de Rust basado en un plato de bicicleta . [24]
El sistema de tipos de Rust experimentó cambios significativos entre las versiones 0.2, 0.3 y 0.4. En la versión 0.2, lanzada en marzo de 2012, se introdujeron clases por primera vez. [25] Cuatro meses después, la versión 0.3 agregó destructores y polimorfismo , mediante el uso de interfaces. [26] En octubre de 2012, se lanzó la versión 0.4, que agregó rasgos como medio de herencia . Las interfaces se combinaron con rasgos y se eliminaron como una característica separada; y las clases fueron reemplazadas por una combinación de implementaciones y tipos estructurados . [27]
A principios de la década de 2010, la gestión de la memoria a través del sistema de propiedad se consolidó gradualmente para evitar errores de memoria. En 2013, se eliminó el recolector de basura de Rust y se implementaron las reglas de propiedad. [20]
En enero de 2014, el editor en jefe del Dr. Dobb's Journal , Andrew Binstock, comentó sobre las posibilidades de Rust de convertirse en un competidor de C++ , junto con D , Go y Nim (entonces Nimrod). Según Binstock, si bien Rust era "ampliamente visto como un lenguaje notablemente elegante", la adopción se ralentizó porque cambiaba radicalmente de una versión a otra. [28] La primera versión estable , Rust 1.0, se anunció el 15 de mayo de 2015. [29] [30]
El desarrollo del motor de navegador Servo continuó junto con el propio crecimiento de Rust. En septiembre de 2017, se lanzó Firefox 57 como la primera versión que incorporaba componentes de Servo, en un proyecto denominado " Firefox Quantum ". [31]
En agosto de 2020, Mozilla despidió a 250 de sus 1.000 empleados en todo el mundo, como parte de una reestructuración corporativa provocada por la pandemia de COVID-19 . [32] [33] El equipo detrás de Servo se disolvió. El evento generó preocupaciones sobre el futuro de Rust, ya que algunos miembros del equipo contribuyeron activamente a Rust. [34] En la semana siguiente, el Rust Core Team reconoció el severo impacto de los despidos y anunció que los planes para una fundación Rust estaban en marcha. El primer objetivo de la fundación sería hacerse cargo de todas las marcas comerciales y nombres de dominio , y asumir la responsabilidad financiera de sus costes. [35]
El 8 de febrero de 2021, sus cinco empresas fundadoras ( AWS , Huawei , Google , Microsoft y Mozilla ) anunciaron la formación de la Fundación Rust . [36] [37] En una publicación de blog publicada el 6 de abril de 2021, Google anunció soporte para Rust dentro del Proyecto de código abierto de Android como alternativa a C/C++. [38]
El 22 de noviembre de 2021, el Equipo de Moderación, que era responsable de hacer cumplir los estándares comunitarios y el Código de Conducta, anunció su renuncia "en protesta por el hecho de que el Equipo Central no rinde cuentas a nadie más que a ellos mismos". [39] En mayo de 2022, el equipo central de Rust, otros programadores principales y ciertos miembros de la junta directiva de la Fundación Rust implementaron reformas de gobernanza en respuesta al incidente. [40]
La Fundación Rust publicó un borrador de una nueva política de marcas el 6 de abril de 2023, revisando sus reglas sobre cómo se puede usar el logotipo y el nombre de Rust, lo que generó reacciones negativas por parte de los usuarios y contribuyentes de Rust. [41]
La sintaxis de Rust es similar a la de C y C++, [42] [43] aunque muchas de sus características fueron influenciadas por lenguajes de programación funcionales . [44] Hoare describió a Rust como dirigido a "desarrolladores de C++ frustrados" y enfatizó características como la seguridad, el control del diseño de la memoria y la concurrencia . [21] La seguridad en Rust incluye las garantías de seguridad de la memoria, seguridad de tipos y falta de carreras de datos.
A continuación se muestra un "¡Hola, mundo!" programa en Rust. La fn
palabra clave denota una función y la println!
macro imprime el mensaje en la salida estándar . [45] Las declaraciones en Rust están separadas por punto y coma.
fn principal () { imprimirln! ( "¡Hola Mundo!" ); }
En Rust, los bloques de código están delimitados por llaves y el flujo de control se implementa mediante palabras clave que incluyen if
,, y . [46] La coincidencia de patrones se puede realizar utilizando la palabra clave. [47] En los ejemplos siguientes, las explicaciones se dan en comentarios , que comienzan con . [48]else
while
for
match
//
fn main () { // Definiendo una variable mutable con 'let mut' // Usando la macro vec! para crear un vector let mut value = vec! [ 1 , 2 , 3 , 4 ]; para valor en & valores { println! ( "valor = {}" , valor ); } si valores . len () > 5 { imprimirln! ( "La lista tiene más de cinco elementos" ); } // Coincidencia de patrones con valores coincidentes . len () { 0 => imprimirln! ( "Vacío" ), 1 => println! ( "Un valor" ), // la coincidencia de patrones puede usar rangos de números enteros 2 ..= 10 => println! ( "Entre dos y diez valores" ), 11 => println! ( "Once valores" ), // Un patrón `_` se llama "comodín", coincide con cualquier valor _ => println! ( "Muchos valores" ), }; // bucle while con coincidencia de predicados y patrones usando let while let Some ( valor ) = valores . pop () { imprimir! ( "valor = {valor}" ); // usando llaves para formatear una variable local } }
Rust está orientado a expresiones , y casi todas las partes del cuerpo de una función son una expresión , incluidos los operadores de flujo de control. [49]if
Se utiliza la expresión ordinaria en lugar del condicional ternario de C. Dado que los retornos son implícitos, no es necesario que una función termine con una return
expresión; si se omite el punto y coma, el valor de la última expresión de la función se utiliza como valor de retorno , [50] como se ve en la siguiente implementación recursiva de la función factorial :
fn factorial ( i : u64 ) -> u64 { si i == 0 { 1 } else { i * factorial ( i - 1 ) } }
La siguiente implementación iterativa utiliza el ..=
operador para crear un rango inclusivo:
fn factorial ( i : u64 ) -> u64 { ( 2 ..= i ). producto () }
En Rust, las funciones anónimas se denominan cierres. [51] Se definen utilizando la siguiente sintaxis:
|< parámetro - nombre > : < tipo >| - > <retorno-tipo> { <cuerpo> } ;
Por ejemplo:
sea f = | x : i32 | -> i32 { x * 2 };
Sin embargo, con la inferencia de tipos, el compilador puede inferir el tipo de cada parámetro y el tipo de retorno, por lo que el formulario anterior se puede escribir como:
sea f = | x | { x * 2 };
Con cierres con una sola expresión (es decir, un cuerpo con una línea) y tipo de retorno implícito, se pueden omitir las llaves:
sea f = | x | x * 2 ;
Los cierres sin parámetro de entrada se escriben así:
sea f = || imprimir! ( "¡Hola Mundo!" );
Los cierres se pueden pasar como parámetros de entrada de funciones que esperan un puntero de función:
// Una función que toma un puntero de función como argumento y lo llama con // el valor `5`. fn apply ( f : fn ( i32 ) -> i32 ) -> i32 { // Sin punto y coma, para indicar un retorno implícito f ( 5 ) } fn main () { // Definiendo el cierre let f = | x | x * 2 ; imprimir! ( "{}" , aplicar ( f )); // 10 imprimir! ( "{}" , f ( 5 )); // 10 }
Sin embargo, es posible que se necesiten reglas complejas para describir cómo se capturan los valores en el cuerpo del cierre. Se implementan utilizando los rasgos Fn
, FnMut
y FnOnce
: [52]
Fn
: el cierre captura por referencia ( &T
). Se utilizan para funciones que aún se pueden llamar si solo tienen acceso de referencia (con &
) a su entorno.FnMut
: el cierre captura por referencia mutable ( &mut T
). Se utilizan para funciones que se pueden llamar si tienen acceso de referencia mutable (con &mut
) a su entorno.FnOnce
: el cierre captura por valor ( T
). Se utilizan para funciones que sólo se llaman una vez.Con estas características, el compilador capturará variables de la manera menos restrictiva posible. [52] Ayudan a regular cómo se mueven los valores entre ámbitos, lo cual es muy importante ya que Rust sigue una construcción de por vida para garantizar que los valores se "tomen prestados" y se muevan de una manera predecible y explícita. [53]
A continuación se demuestra cómo se puede pasar un cierre como parámetro de entrada utilizando el Fn
rasgo:
// Una función que toma un valor de tipo F (que se define como // un tipo genérico que implementa el rasgo `Fn`, por ejemplo, un cierre) // y lo llama con el valor `5`. fn apply_by_ref < F > ( f : F ) -> i32 donde F : Fn ( i32 ) -> i32 { f ( 5 ) } fn principal () { sea f = | x | { imprimir! ( "Obtuve el valor: {}" , x ); x * 2 }; // Aplica la función antes de imprimir su valor de retorno println! ( "5 * 2 = {}" , apply_by_ref ( f )); } // ~~ Salida del programa ~~ // Obtuve el valor: 5 // 5 * 2 = 10
La definición de función anterior también se puede acortar por conveniencia de la siguiente manera:
fn apply_by_ref ( f : impl Fn ( i32 ) -> i32 ) -> i32 { f ( 5 ) }
Rust está fuertemente tipado y estático . Los tipos de todas las variables deben conocerse en el momento de la compilación; asignar un valor de un tipo particular a una variable de tipo diferente provoca un error de compilación . Las variables se declaran con la palabra clave let
y se utiliza la inferencia de tipos para determinar su tipo. [54] Las variables asignadas varias veces deben marcarse con la palabra clave mut
(abreviatura de mutable). [55]
El tipo de entero predeterminado es i32
y el tipo de punto flotante predeterminado es f64
. Si el tipo de un número literal no se proporciona explícitamente, se infiere del contexto o se utiliza el tipo predeterminado. [56]
Option
los valores se manejan usando azúcar sintáctico , como la if let
construcción, para acceder al valor interno (en este caso, una cadena): [76]
fn main () { let nombre1 : Opción <& str > = Ninguna ; // En este caso, no se imprimirá nada si let Some ( nombre ) = nombre1 { println! ( "{nombre}" ); } let nombre2 : Opción <& str > = Algunos ( "Matthew" ); // En este caso, la palabra "Matthew" se imprimirá si let Some ( nombre ) = nombre2 { println! ( "{nombre}" ); } }
Rust no utiliza punteros nulos para indicar una falta de datos, ya que hacerlo puede provocar una desreferenciación nula . En consecuencia, se garantiza que las referencias básicas &
y &mut
no serán nulas. En cambio, Rust usa Option
para este propósito: Some(T)
indica que un valor está presente y None
es análogo al puntero nulo. [77] Option
implementa una "optimización de puntero nulo", evitando cualquier sobrecarga espacial para tipos que no pueden tener un valor nulo (referencias o tipos NonZero
, por ejemplo). [78]
A diferencia de las referencias, el puntero sin formato escribe *const
y *mut
puede ser nulo; sin embargo, es imposible eliminar la referencia a ellos a menos que el código se declare explícitamente inseguro mediante el uso de un unsafe
bloque. A diferencia de la desreferenciación, la creación de punteros sin formato se permite dentro del código Rust seguro. [79]
Los tipos definidos por el usuario se crean con las palabras clave struct
o enum
. La struct
palabra clave se utiliza para indicar un tipo de registro que agrupa varios valores relacionados. [80] enum
s puede adoptar diferentes variantes en tiempo de ejecución, con capacidades similares a los tipos de datos algebraicos que se encuentran en los lenguajes de programación funcionales. [81] Tanto las estructuras como las enumeraciones pueden contener campos con diferentes tipos. [82] Se pueden definir nombres alternativos para el mismo tipo con la type
palabra clave. [83]
La impl
palabra clave puede definir métodos para un tipo definido por el usuario (los datos y las funciones se definen por separado). Las implementaciones cumplen un papel similar al de las clases dentro de otros lenguajes. [84]
Rust no proporciona conversión de tipos implícita (coerción) entre tipos primitivos. Sin embargo, la conversión de tipo explícita (casting) se puede realizar utilizando la as
palabra clave. [85]
sea x = 1000 ; imprimir! ( "1000 como u16 es: {}" , x como u16 );
El sistema de propiedad de Rust consta de reglas que garantizan la seguridad de la memoria sin utilizar un recolector de basura. En el momento de la compilación, cada valor debe estar asociado a una variable denominada propietario de ese valor, y cada valor debe tener exactamente un propietario. [86] Los valores se mueven entre diferentes propietarios mediante asignación o pasando un valor como parámetro de función. Los valores también se pueden tomar prestados, lo que significa que se pasan temporalmente a una función diferente antes de devolverlos al propietario. [87] Con estas reglas, Rust puede evitar la creación y el uso de punteros colgantes : [87] [88]
fn print_string ( s : Cadena ) { println! ( "{}" , s ); } fn main () { let s = String :: from ( "Hola mundo" ); imprimir_cadena ( s ); // s consumidos por print_string // s se ha movido, por lo que ya no se puede utilizar // otro print_string(s); resultaría en un error de compilación }
Debido a estas reglas de propiedad, los tipos de Rust se conocen como tipos lineales o afines , lo que significa que cada valor se puede usar exactamente una vez. Esto impone una forma de aislamiento de fallas de software, ya que el propietario de un valor es el único responsable de su corrección y desasignación. [89]
Cuando un valor sale del alcance, se elimina ejecutando su destructor . El destructor se puede definir mediante programación mediante la implementación del Drop
rasgo. Esto ayuda a administrar recursos como identificadores de archivos, sockets de red y bloqueos , ya que cuando se eliminan objetos, los recursos asociados a ellos se cierran o liberan automáticamente. [90]
La vida útil suele ser una parte implícita de todos los tipos de referencia en Rust. Cada duración abarca un conjunto de ubicaciones en el código para las cuales una variable es válida. Por ejemplo, una referencia a una variable local tiene una vida útil correspondiente al bloque en el que está definida: [91]
fn principal () { sea x = 5 ; // ------------------+- Duración 'b // | sea r = & x ; // -+-- Duración 'a | // | | imprimir! ( "r: {}" , r ); // | | // | | // -+ | } // ------------------+
El verificador de préstamos en el compilador de Rust utiliza tiempos de vida para garantizar que los valores a los que apunta una referencia sigan siendo válidos. [92] [93] En el ejemplo anterior, almacenar una referencia a la variable x
es r
válido, ya que la variable x
tiene una vida útil más larga ( 'b
) que la variable r
( 'a
). Sin embargo, cuando x
tiene una vida útil más corta, el verificador de préstamos rechazará el programa:
fn principal () { sea r ; // ------------------+- Duración 'a // | { // | sea x = 5 ; // -+-- Duración 'b | r = x ; // | | } // | | // | imprimir! ( "r: {}" , r ); // | } // ------------------+
Dado que la vida útil de la variable referenciada ( 'b
) es más corta que la vida útil de la variable que contiene la referencia ( 'a
), el verificador de préstamo genera errores, lo que impide x
que se utilice desde fuera de su alcance. [94]
Rust define la relación entre la vida útil de los objetos creados y utilizados por las funciones, utilizando parámetros de vida útil , como característica distintiva. [95]
El siguiente ejemplo analiza algunas opciones de configuración de una cadena y crea una estructura que contiene las opciones. La estructura sólo contiene referencias a los datos; por lo tanto, para que la estructura siga siendo válida, los datos a los que hace referencia la estructura también deben ser válidos. La firma de la función parse_config
especifica explícitamente esta relación. En este ejemplo, los tiempos de vida explícitos son innecesarios en las versiones más nuevas de Rust, debido a la elisión de tiempos de vida, que es un algoritmo que asigna automáticamente tiempos de vida a funciones si son triviales. [96]
use std :: colecciones :: HashMap ; // Esta estructura tiene un parámetro de duración, 'src. El nombre sólo se utiliza dentro de la definición de la estructura. #[derive(Debug)] struct Config <' src > { nombre de host : & ' src str , nombre de usuario : & ' src str , } // Esta función también tiene un parámetro de duración, 'cfg. 'cfg se adjunta al parámetro "config", que // establece que los datos en "config" duran al menos tanto como la vida útil de 'cfg. // La estructura devuelta también usa 'cfg durante su vida útil, por lo que puede vivir como máximo tanto como 'cfg. fn parse_config <' cfg > ( config : & ' cfg str ) -> Config <' cfg > { let key_values : HashMap < _ , _ > = config . líneas () . filtro ( | línea | ! línea . comienza_con ( '#' )) . filter_map ( | línea | línea . split_once ( '=' )) . mapa ( | ( clave , valor ) | ( clave . trim (), valor . trim ())) . recolectar (); Configuración { nombre de host : valores_clave [ "nombre de host" ], nombre de usuario : valores_clave [ "nombre de usuario" ], } } fn main () { let config = parse_config ( r#"hostname = foobar nombre de usuario=barfoo"# , ); imprimir! ( "Configuración analizada: {:#?}" , configuración ); }
Rust está diseñado para ser seguro para la memoria . No permite punteros nulos, punteros colgantes ni carreras de datos . [97] [98] [99] Los valores de datos sólo se pueden inicializar a través de un conjunto fijo de formularios, todos los cuales requieren que sus entradas ya estén inicializadas. [100]
El código inseguro puede subvertir algunas de estas restricciones utilizando la unsafe
palabra clave. [79] El código inseguro también se puede utilizar para funcionalidades de bajo nivel, como acceso a memoria volátil , intrínsecos específicos de la arquitectura, juegos de palabras y ensamblaje en línea. [101]
Rust no utiliza la recolección de basura . En cambio, la memoria y otros recursos se administran mediante la convención "la adquisición de recursos es inicialización", [102] con recuento de referencias opcional . Rust proporciona una gestión determinista de los recursos, con muy bajos gastos generales . [103] Los valores se asignan en la pila de forma predeterminada y todas las asignaciones dinámicas deben ser explícitas. [104]
Los tipos de referencia integrados que utilizan el &
símbolo no implican el recuento de referencias en tiempo de ejecución. La seguridad y validez de los punteros subyacentes se verifica en el momento de la compilación, lo que evita punteros colgantes y otras formas de comportamiento indefinido . [105] El sistema de tipos de Rust separa las referencias compartidas e inmutables del formulario &T
de las referencias únicas y mutables del formulario &mut T
. Una referencia mutable puede convertirse en una referencia inmutable, pero no al revés. [106]
Las características más avanzadas de Rust incluyen el uso de funciones genéricas . Una función genérica recibe parámetros genéricos , que permiten aplicar la misma función a diferentes tipos de variables. Esta capacidad reduce el código duplicado [107] y se conoce como polimorfismo paramétrico .
El siguiente programa calcula la suma de dos cosas, para lo cual la suma se implementa mediante una función genérica:
use std :: ops :: Agregar ; // suma es una función genérica con un parámetro de tipo, T fn suma < T > ( num1 : T , num2 : T ) -> T donde T : Agregar < Salida = T > , // T debe implementar el rasgo Agregar donde la suma devuelve otra T { num1 + num2 // num1 + num2 es azúcar sintáctico para num1.add(num2) proporcionado por Agregar rasgo } fn main () { let resultado1 = suma ( 10 , 20 ); imprimir! ( "La suma es: {}" , resultado1 ); // La suma es: 30 sea resultado2 = suma ( 10.23 , 20.45 ); imprimir! ( "La suma es: {}" , resultado2 ); // La suma es: 30,68 }
sum
En el momento de la compilación, se crean instancias de funciones polimórficas como con los tipos específicos que requiere el código; en este caso, suma de números enteros y suma de flotantes.
Los genéricos se pueden utilizar en funciones para permitir implementar un comportamiento para diferentes tipos sin repetir el mismo código. Las funciones genéricas se pueden escribir en relación con otros genéricos, sin conocer el tipo real. [108]
El sistema de tipos de Rust admite un mecanismo llamado rasgos, inspirado en las clases de tipos del lenguaje Haskell , [5] para definir el comportamiento compartido entre diferentes tipos. Por ejemplo, el Add
rasgo se puede implementar para flotantes y números enteros, que se pueden sumar; y los rasgos Display
o Debug
se pueden implementar para cualquier tipo que pueda convertirse en una cadena. Los rasgos se pueden utilizar para proporcionar un conjunto de comportamientos comunes para diferentes tipos sin conocer el tipo real. Esta facilidad se conoce como polimorfismo ad hoc .
Las funciones genéricas pueden restringir el tipo genérico para implementar uno o varios rasgos en particular; por ejemplo, una add_one
función podría requerir el tipo para implementarse Add
. Esto significa que se puede verificar el tipo de una función genérica tan pronto como se define. La implementación de genéricos es similar a la implementación típica de plantillas de C++: se genera una copia separada del código para cada instancia. Esto se llama monomorfización y contrasta con el esquema de borrado de tipos utilizado normalmente en Java y Haskell. El borrado de tipos también está disponible mediante la palabra clave dyn
(abreviatura de dinámico). [109] Debido a que la monomorfización duplica el código para cada tipo utilizado, puede dar como resultado un código más optimizado para casos de uso específicos, pero el tiempo de compilación y el tamaño del binario de salida también aumentan. [110]
Además de definir métodos para un tipo definido por el usuario, la impl
palabra clave se puede utilizar para implementar un rasgo para un tipo. [84] Los rasgos pueden proporcionar métodos derivados adicionales cuando se implementan. [111] Por ejemplo, el rasgo Iterator
requiere que el next
método esté definido para el tipo. Una vez definido el next
método, el rasgo puede proporcionar métodos auxiliares funcionales comunes sobre el iterador, como map
o filter
. [112]
Los rasgos de Rust se implementan mediante despacho estático , lo que significa que el tipo de todos los valores se conoce en el momento de la compilación; sin embargo, Rust también utiliza una característica conocida como objetos de rasgo para lograr el envío dinámico (también conocido como tipificación de pato ). [113] Los objetos de rasgo enviados dinámicamente se declaran utilizando la sintaxis dyn Tr
donde Tr
hay un rasgo. Los objetos de rasgo tienen un tamaño dinámico, por lo que deben colocarse detrás de un puntero, como por ejemplo Box
. [114] El siguiente ejemplo crea una lista de objetos donde cada objeto se puede imprimir usando el Display
rasgo:
use std :: fmt :: Pantalla ; let v : Vec < Box < dyn Display >> = vec! [ Cuadro :: nuevo ( 3 ), Cuadro :: nuevo ( 5.0 ), Cuadro :: nuevo ( " hola " ), ]; para x en v { println! ( "{X}" ); }
Si un elemento de la lista no implementa el Display
rasgo, provocará un error en tiempo de compilación. [115]
Los bucles For en Rust funcionan en un estilo funcional como operaciones sobre un tipo de iterador . Por ejemplo, en el bucle
para x en 0 .. 100 { f ( x ); }
0..100
es un valor de tipo Range
que implementa el Iterator
rasgo; el código aplica la función f
a cada elemento devuelto por el iterador. Los iteradores se pueden combinar con funciones sobre iteradores como map
, filter
y sum
. Por ejemplo, lo siguiente suma todos los números entre 1 y 100 que son múltiplos de 3:
( 1 ..= 100 ). filtro ( |& x | x % 3 == 0 ). suma ()
Es posible ampliar el lenguaje Rust mediante macros.
Una macro declarativa (también llamada "macro por ejemplo") es una macro que utiliza la coincidencia de patrones para determinar su expansión. [116] [117]
Las macros de procedimiento son funciones de Rust que ejecutan y modifican el flujo de token de entrada del compilador , antes de que se compile cualquier otro componente. Generalmente son más flexibles que las macros declarativas, pero son más difíciles de mantener debido a su complejidad. [118] [119]
Las macros de procedimiento vienen en tres versiones:
custom!(...)
#[derive(CustomDerive)]
#[custom_attribute]
La println!
macro es un ejemplo de una macro similar a una función. La serde_derive
macro [120] proporciona una biblioteca de uso común para generar código para leer y escribir datos en muchos formatos, como JSON . Las macros de atributos se usan comúnmente para enlaces de idiomas , como la extendr
biblioteca para enlaces de Rust a R. [121]
El siguiente código muestra el uso de las macros de procedimiento derivadas Serialize
de , Deserialize
y Debug
- para implementar la lectura y escritura JSON, así como la capacidad de formatear una estructura para la depuración.
utilice serde_json :: { Serializar , Deserializar }; #[derive(Serializar, Deserializar, Depurar)] Punto de estructura { x : i32 , y : i32 , } fn main () { let punto = Punto { x : 1 , y : 2 }; let serializado = serde_json :: to_string ( & punto ). desenvolver (); imprimir! ( "serializado = {}" , serializado ); dejar deserializado : Punto = serde_json :: from_str ( & serializado ). desenvolver (); imprimir! ( "deserializado = {:?}" , deserializado ); }
Rust no admite argumentos variados en funciones. En su lugar, utiliza macros . [122]
macro_reglas! calcular { // El patrón para una sola `eval` ( eval $e : expr ) => {{ { let val : usize = $e ; // ¡Forzar que los tipos sean números enteros println! ( "{} = {}" , ¡stringificar! { $e }, val ); } }}; // Descomponer múltiples `eval`s recursivamente ( eval $e : expr , $( eval $es : expr ), + ) => {{ calcular ! { eval $e } calcular ! { $( eval $es ), + } }}; } fn principal () { calcular ! { // ¡Mira mamá! Variádico `¡calcular!`! evaluación 1 + 2 , evaluación 3 + 4 , evaluación ( 2 * 3 ) + 1 } }
c_variadic
interruptor de función. Al igual que con otras interfaces C, el sistema se considera unsafe
Rust. [123]Rust tiene una interfaz de función externa (FFI) que se puede usar tanto para llamar a código escrito en lenguajes como C desde Rust como para llamar a código de Rust desde esos lenguajes. A partir de 2023, existe una biblioteca externa llamada CXX para realizar llamadas hacia o desde C++. [124] Rust y C difieren en cómo distribuyen las estructuras en la memoria, por lo que a las estructuras de Rust se les puede dar un #[repr(C)]
atributo, forzando el mismo diseño que la estructura C equivalente. [125]
El ecosistema Rust incluye su compilador, su biblioteca estándar y componentes adicionales para el desarrollo de software. La instalación de componentes normalmente la gestiona rustup
, un instalador de la cadena de herramientas de Rust desarrollado por el proyecto Rust. [126]
El compilador de Rust se llama rustc
y traduce el código de Rust a un lenguaje de bajo nivel llamado representación intermedia LLVM (LLVM IR). Luego se invoca LLVM como un subcomponente para traducir el código IR a código de máquina . Luego se utiliza un vinculador para combinar varias cajas como un único archivo ejecutable o binario. [127] [128]
Además de LLVM, el compilador también admite el uso de backends alternativos como GCC y Cranelift para la generación de código. [129] La intención de esos backends alternativos es aumentar la cobertura de la plataforma de Rust o mejorar los tiempos de compilación. [130] [131]
La biblioteca estándar de Rust define e implementa muchos tipos de datos personalizados ampliamente utilizados, incluidas estructuras de datos centrales como Vec
, Option
y HashMap
, así como tipos de punteros inteligentes . Rust también proporciona una manera de excluir la mayor parte de la biblioteca estándar usando el atributo #![no_std]
; esto permite aplicaciones, como dispositivos integrados, que desean eliminar el código de dependencia o proporcionar sus propias estructuras de datos centrales. Internamente, la biblioteca estándar se divide en tres partes, core
, alloc
y std
, donde std
y alloc
están excluidos por #![no_std]
. [132]
Cargo es el sistema de compilación y administrador de paquetes de Rust . Descarga, compila, distribuye y carga paquetes (llamados cajas ) que se mantienen en un registro oficial. También actúa como interfaz para Clippy y otros componentes de Rust. [dieciséis]
De forma predeterminada, Cargo obtiene sus dependencias del registro aportado por el usuario crates.io , pero los repositorios Git y las cajas en el sistema de archivos local y otras fuentes externas también se pueden especificar como dependencias. [133]
Rustfmt es un formateador de código para Rust. Da formato a los espacios en blanco y la sangría para producir código de acuerdo con un estilo común , a menos que se especifique lo contrario. Se puede invocar como un programa independiente o desde un proyecto de Rust a través de Cargo. [134]
Clippy es la herramienta de linting incorporada de Rust para mejorar la corrección, el rendimiento y la legibilidad del código Rust. A 2024 [actualizar], cuenta con más de 700 reglas. [135] [136]
Después de Rust 1.0, se desarrollan nuevas funciones en versiones nocturnas que se publican diariamente. Durante cada ciclo de lanzamiento de seis semanas, los cambios en las versiones nocturnas se lanzan a la versión beta, mientras que los cambios de la versión beta anterior se lanzan a una nueva versión estable. [137]
Cada dos o tres años se produce una nueva "edición". Se lanzan ediciones para permitir realizar cambios importantes limitados , como promocionar await
una palabra clave para admitir funciones asíncronas/en espera . Las cajas dirigidas a diferentes ediciones pueden interoperar entre sí, por lo que una caja puede actualizarse a una nueva edición incluso si quienes llaman o sus dependencias todavía apuntan a ediciones anteriores. La migración a una nueva edición se puede facilitar con herramientas automatizadas. [138]
El servidor de idiomas más popular para Rust es Rust Analyzer , que reemplazó oficialmente al servidor de idiomas original, RLS , en julio de 2022. [139] Rust Analyzer proporciona a los IDE y editores de texto información sobre un proyecto de Rust; Funciones básicas que incluyen el autocompletado y la visualización de errores de compilación durante la edición. [140]
En general, las garantías de seguridad de la memoria de Rust no imponen una sobrecarga de tiempo de ejecución. [141] Una excepción notable es la indexación de matrices que se verifica en tiempo de ejecución, aunque esto a menudo no afecta el rendimiento. [142] Dado que no realiza recolección de basura, Rust suele ser más rápido que otros lenguajes seguros para la memoria. [143] [144] [145]
Rust proporciona dos "modos": seguro e inseguro. El modo seguro es el "normal", en el que está escrita la mayor parte de Rust. En modo inseguro, el desarrollador es responsable de la seguridad de la memoria del código, lo que resulta útil en casos en los que el compilador es demasiado restrictivo. [146]
Muchas de las características de Rust son las llamadas abstracciones de costo cero , lo que significa que se optimizan en tiempo de compilación y no incurren en penalizaciones de tiempo de ejecución. [147] El sistema de propiedad y préstamo permite implementaciones de copia cero para algunas tareas sensibles al rendimiento, como el análisis . [148] El envío estático se utiliza de forma predeterminada para eliminar las llamadas a métodos , con la excepción de los métodos llamados en objetos de rasgos dinámicos. [149] El compilador también utiliza expansión en línea para eliminar llamadas a funciones y llamadas a métodos distribuidos estáticamente. [150]
Dado que Rust utiliza LLVM , cualquier mejora de rendimiento en LLVM también se traslada a Rust. [151] A diferencia de C y C++, Rust permite reordenar los elementos struct y enum [152] para reducir los tamaños de las estructuras en la memoria, para una mejor alineación de la memoria y para mejorar la eficiencia del acceso a la caché . [153]
Rust se ha utilizado en software en diferentes dominios. Rust fue financiado inicialmente por Mozilla como parte del desarrollo de Servo, un motor de navegador paralelo experimental, en colaboración con Samsung . [154] Los componentes del motor Servo se incorporaron más tarde en el motor del navegador Gecko subyacente a Firefox. [155] En enero de 2023, Google ( Alphabet ) anunció soporte para bibliotecas Rust de terceros en Chromium y, en consecuencia, en el código base de ChromeOS . [156]
Rust se utiliza en varios proyectos de software backend de grandes servicios web . OpenDNS , un servicio de resolución de DNS propiedad de Cisco , utiliza Rust internamente. [157] [158] Amazon Web Services comenzó a desarrollar proyectos en Rust ya en 2017, [159] incluido Firecracker , una solución de virtualización; [160] Bottlerocket, una solución de distribución y contenedorización de Linux; [161] y Tokio , una pila de redes asincrónicas. [162] Microsoft Azure IoT Edge, una plataforma utilizada para ejecutar servicios de Azure en dispositivos IoT , tiene componentes implementados en Rust. [163] Microsoft también utiliza Rust para ejecutar módulos en contenedores con WebAssembly y Kubernetes . [164] Cloudflare , una empresa que proporciona servicios de red de entrega de contenido, utiliza Rust para su motor de coincidencia de patrones de firewall y el servidor web Pingora. [165] [166]
En los sistemas operativos, se ha agregado soporte para Rust a Linux [167] [168] y Android . [169] [170] Microsoft está reescribiendo partes de Windows en Rust. [171] El proyecto r9 tiene como objetivo volver a implementar el Plan 9 de Bell Labs en Rust. [172] Rust se ha utilizado en el desarrollo de nuevos sistemas operativos como Redox , un sistema operativo y microkernel "similar a Unix" , [173] Theseus, un sistema operativo experimental con gestión de estado modular, [174] [175] y la mayor parte de fucsia . [176] Rust también se utiliza para herramientas de línea de comandos y componentes del sistema operativo, incluido stratisd , un administrador de sistemas de archivos [177] [178] y COSMIC, un entorno de escritorio de System76 . [179] [180]
En el desarrollo web, el administrador de paquetes npm comenzó a usar Rust en producción en 2019. [181] [182] [183] Deno , un tiempo de ejecución seguro para JavaScript y TypeScript , está construido con V8 , Rust y Tokio. [184] Otras adopciones notables en este espacio incluyen Ruffle , un emulador SWF de código abierto , [185] y Polkadot , una plataforma de criptomonedas y blockchain de código abierto . [186]
Discord , una plataforma social de mensajería instantánea, utiliza Rust para partes de su backend, así como para la codificación de vídeo del lado del cliente . [187] En 2021, Dropbox anunció el uso de Rust para un servicio de captura de pantalla, vídeo e imágenes. [188] Facebook ( Meta ) utilizó Rust para Mononoke, un servidor para el sistema de control de versiones Mercurial . [189]
En la encuesta para desarrolladores de Stack Overflow de 2023 , el 13% de los encuestados había realizado recientemente un desarrollo extenso en Rust. [190] La encuesta también nombró a Rust como el "lenguaje de programación más querido" cada año desde 2016 hasta 2023 (inclusive), según la cantidad de desarrolladores interesados en continuar trabajando en el mismo lenguaje. [191] [nota 8] En 2023, Rust fue la sexta "tecnología más buscada", y el 31% de los desarrolladores que actualmente no trabajan en Rust expresaron interés en hacerlo. [190]
La Rust Foundation es una organización sin fines de lucro constituida en Estados Unidos , cuyo objetivo principal es respaldar el proyecto técnico como entidad legal y ayudar a administrar la marca y los activos de infraestructura. [194] [43]
Se estableció el 8 de febrero de 2021 con cinco miembros corporativos fundadores (Amazon Web Services, Huawei, Google, Microsoft y Mozilla). [195] La junta directiva de la fundación está presidida por Shane Miller. [196] A partir de finales de 2021, su directora ejecutiva y directora ejecutiva es Rebecca Rumbul. [197] Antes de esto, Ashley Williams fue directora ejecutiva interina. [198]
El proyecto Rust está compuesto por equipos que se encargan de diferentes subáreas del desarrollo. El equipo del compilador desarrolla, gestiona y optimiza los componentes internos del compilador; y el equipo de idiomas diseña nuevas funciones del lenguaje y ayuda a implementarlas. El sitio web del proyecto Rust enumera 9 equipos de primer nivel a partir de enero de 2024 [actualizar]. [199] Los representantes de los equipos forman el consejo de liderazgo, que supervisa el proyecto Rust en su conjunto. [200]
i32
por defecto o inferido del contexto.str
ya String
son siempre válidos en UTF-8 y pueden contener ceros internos.Aquellos de ustedes que estén familiarizados con el estilo de Elm pueden reconocer que los mensajes
--explicar
actualizados se inspiran en gran medida en el enfoque de Elm.
Están inspirados en... tipos de propiedad y punteros prestados en el lenguaje de programación Rust.
Ambos son lenguajes entre llaves, con una sintaxis similar a la de C que los hace poco intimidantes para los programadores de C.
Observamos una gran variación en los gastos generales de la indexación comprobada: el 23,6 % de los puntos de referencia informan impactos significativos en el rendimiento gracias a la indexación comprobada, pero el 64,5 % informa poco o ningún impacto y, sorprendentemente, el 11,8 % informa un mejor rendimiento... En última instancia , si bien la indexación no controlada puede mejorar el rendimiento, la mayoría de las veces no es así.
... Mientras que algunos compiladores (por ejemplo, Rust) admiten la reordenación de estructuras [82], los compiladores de C y C++ tienen prohibido reordenar estructuras de datos (por ejemplo, estructura o clase) [74]...