Rust es un lenguaje de programación de propósito general que enfatiza el rendimiento , la seguridad de tipos y la concurrencia . Aplica la seguridad de la memoria , lo que significa que todas las referencias apuntan a una memoria válida. Lo hace sin un recolector de basura tradicional ; en cambio, tanto los errores de seguridad de la memoria como las carreras de datos se evitan mediante el "verificador de préstamos", que rastrea la duración de vida de los objetos de las referencias en el momento de la compilación .
Rust no impone un paradigma de programación , sino que fue influenciado por ideas de la programación funcional , incluyendo inmutabilidad , funciones de orden superior , tipos de datos algebraicos y coincidencia de patrones . También admite la programación orientada a objetos a través de estructuras, enumeraciones , rasgos y métodos. 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 ( Alphabet ), Meta y Microsoft . En diciembre de 2022, se convirtió en el primer lenguaje distinto de C y ensamblador en ser compatible con el desarrollo del kernel de Linux .
Rust se ha destacado por su rápida adopción y ha sido estudiado en investigaciones sobre la teoría del lenguaje de programación .
Rust comenzó como un proyecto personal en 2006 por parte del empleado de Mozilla Research Graydon Hoare, llamado así por el grupo de hongos que están "sobrediseñados para sobrevivir". Mozilla comenzó a patrocinar el proyecto en 2009 y emplearía a una docena de ingenieros para trabajar en él a tiempo completo durante los siguientes diez años. [16]
Alrededor de 2010, el trabajo pasó del compilador inicial escrito en OCaml a un compilador autoalojado basado en LLVM escrito en Rust. El nuevo compilador de Rust se compiló con éxito en 2011. [17] [ Se necesita una mejor fuente ] En el otoño de 2011, se desarrolló el logotipo de Rust basado en un plato de bicicleta . [18]
El sistema de tipos de Rust sufrió cambios significativos entre las versiones 0.2, 0.3 y 0.4. En la versión 0.2, que se lanzó en marzo de 2012, se introdujeron las clases por primera vez. [19] Cuatro meses después, la versión 0.3 agregó destructores y polimorfismo , mediante el uso de interfaces. [20] En octubre de 2012, se lanzó la versión 0.4, que agregó rasgos como un medio de herencia . Las interfaces se combinaron con rasgos y se eliminaron como una característica separada; y las clases se reemplazaron por una combinación de implementaciones y tipos estructurados . [21]
A principios de la década de 2010, la gestión de 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 , pero se mantuvieron las reglas de propiedad. [16]
En enero de 2014, el editor en jefe de 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 desaceleró porque cambió radicalmente de una versión a otra. [22] La primera versión estable , Rust 1.0, se lanzó el 15 de mayo de 2015. [16]
El desarrollo del motor de navegación Servo continuó junto con el crecimiento de Rust. En septiembre de 2017, se lanzó Firefox 57 como la primera versión que incorporó componentes de Servo, en un proyecto llamado " Firefox Quantum ". [23]
En agosto de 2020, Mozilla despidió a 250 de sus 1.000 empleados en todo el mundo, como parte de una reestructuración corporativa causada por la pandemia de COVID-19 . [24] [25] El equipo detrás de Servo se disolvió. El evento generó inquietudes sobre el futuro de Rust, ya que algunos miembros del equipo eran contribuyentes activos de Rust. [26] En la semana siguiente, el equipo central de Rust 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 tomar posesión de todas las marcas comerciales y nombres de dominio , y asumir la responsabilidad financiera de sus costos. [27]
El 8 de febrero de 2021, las cinco empresas fundadoras ( AWS , Huawei , Google , Microsoft y Mozilla ) anunciaron la formación de la Fundación Rust . [28] [29] En una publicación de blog publicada el 6 de abril de 2021, Google anunció el apoyo a Rust dentro del Proyecto de código abierto de Android como alternativa a C/C++. [30]
El 22 de noviembre de 2021, el Equipo de Moderación, responsable de hacer cumplir las normas de la comunidad y el Código de Conducta, anunció su dimisión "en protesta por el hecho de que el Equipo Central se haya puesto a sí mismo al margen de cualquier responsabilidad". [31] 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. [32]
El 6 de abril de 2023, la Fundación Rust publicó un borrador de una nueva política de marca registrada , revisando sus reglas sobre cómo se pueden usar el logotipo y el nombre de Rust, lo que resultó en reacciones negativas de los usuarios y colaboradores de Rust. [33]
La sintaxis de Rust es similar a la de C y C++, [34] [35] aunque muchas de sus características fueron influenciadas por lenguajes de programación funcional como OCaml . [36] 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 . [17] 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 programa "¡Hola, mundo!" en Rust. La fn
palabra clave denota una función y la println!
macro (consulte § Macros) imprime el mensaje en la salida estándar . [37] Las instrucciones en Rust se separan con punto y coma .
fn main () { println! ( "¡Hola, mundo!" ); }
Las variables en Rust se definen a través de la let
palabra clave. [38] El siguiente ejemplo asigna un valor a la variable con nombre foo
.
fn main () { let foo = 10 ; println! ( "El valor de foo es {foo}" ); }
Las variables son inmutables de forma predeterminada, y agregar la mut
palabra clave permite que la variable se mute. [39] El siguiente ejemplo utiliza //
, que denota el comienzo de un comentario . [40]
fn main () { let mut foo = 10 ; // Este código no se compilaría sin agregar "mut". println! ( "El valor de foo es {foo}" ); foo = 20 ; println! ( "El valor de foo es {foo}" ); }
Varias let
expresiones pueden definir múltiples variables con el mismo nombre, lo que se conoce como sombreado de variables . El sombreado de variables permite transformar variables sin tener que nombrarlas de manera diferente. [41] El siguiente ejemplo declara una nueva variable con el mismo nombre que es el doble del valor original:
fn main () { let foo = 10 ; println! ( "El valor de foo es {foo}" ); let foo = foo * 2 ; println! ( "El valor de foo es {foo}" ); }
El sombreado variable también es posible para valores de diferentes tipos, desde una cadena hasta su longitud:
fn main () { let espacios = " " ; let espacios = espacios . len (); }
En Rust, los bloques de código están delimitados por llaves . [42]
if
bloquesUna if
expresión condicional ejecuta código en función de si el valor dado es true
. else
se puede utilizar cuando el valor se evalúa como false
, y se puede utilizar para combinar múltiples expresiones. [43]else if
fn main () { let x = 10 ; if x > 5 { println! ( "el valor es mayor que cinco" ); } si x % 7 == 0 { println! ( "el valor es divisible por 7" ); } de lo contrario si x % 5 == 0 { println! ( "el valor es divisible por 5" ); } de lo contrario { println! ( "el valor no es divisible por 7 o 5" ); } }
while
bucleswhile
se puede utilizar para repetir un bloque de código mientras se cumple una condición. [44]
fn main () { // Iterar sobre todos los números enteros del 4 al 10 let mut valor = 4 ; while valor <= 10 { println! ( "valor = {valor}" ); valor += 1 } }
for
bucles e iteradoresLos bucles for en Rust recorren los elementos de una colección. [45] Las expresiones "for" funcionan sobre cualquier tipo de iterador .
fn main (){ // Uso de `for` con sintaxis de rango para la misma funcionalidad que la anterior para el valor en 4 ..= 10 { println! ( "valor = {valor}" ); } }
En el código anterior, 4..=10
hay un valor de tipo Range
que implementa la Iterator
característica. El código entre llaves se aplica a cada elemento devuelto por el iterador.
Los iteradores se pueden combinar con funciones sobre iteradores como map
, filter
, y sum
. Por ejemplo, la siguiente suma todos los números entre 1 y 100 que son múltiplos de 3:
( 1 ..= 100 ) .filtrar ( |& x | x % 3 == 0 ).suma ( )
loop
y break
declaracionesEn términos más generales, la loop
palabra clave permite repetir una parte del código hasta que break
se produzca un valor. break
Opcionalmente, puede salir del bucle con un valor. Las etiquetas indicadas con se pueden utilizar para interrumpir un bucle externo cuando los bucles están anidados. [46]'label_name
fn main () { let valor = 456 ; let mut x = 1 ; let y = loop { x *= 10 ; if x > valor { break x / 10 ; } }; println! ( "mayor potencia de diez que es menor que valor: {y}" ); deje que mut suba = 1 ; ' externo : bucle { deje que mut baje = 120 ; bucle { si arriba > 100 { romper 'externo ; } si abajo < 4 { romper ; } abajo /= 2 ; arriba += 1 ; println! ( "arriba: {arriba}, abajo: {abajo}" ); } arriba *= 2 ; } }
Rust está orientado a expresiones , y casi todas las partes del cuerpo de una función son expresiones , incluidos los operadores de flujo de control. [47] La if
expresión se utiliza para proporcionar el operador condicional ternario . Como los retornos son implícitos, una función no necesita terminar con una return
expresión; si se omite el punto y coma, el valor de la última expresión en la función se utiliza como valor de retorno , [48] como se ve en la siguiente implementación recursiva de la función factorial :
fn factorial ( i : u64 ) -> u64 { si i == 0 { 1 } de lo contrario { 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 () }
Las expresiones match
and se pueden utilizar para la búsqueda de patrones . Por ejemplo, se puede utilizar para duplicar un valor entero opcional si está presente y devolver cero en caso contrario: [49]if let
match
fn double ( x : Opción < u64 > ) -> u64 { coincidir x { Alguna ( x ) => x * 2 , Ninguna => 0 , } }
De manera equivalente, esto se puede escribir con y :if let
else
fn double ( x : Opción < u64 > ) -> u64 { si deja Alguna ( x ) = x { x * 2 } de lo contrario { 0 } }
Rust es un lenguaje 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 con un tipo diferente provoca un error de compilación . La inferencia de tipos se utiliza para determinar el tipo de las variables si no se especifica. [50]
El tipo entero predeterminado es , y el tipo de punto flotantei32
predeterminado es . Si el tipo de un número literal no se proporciona explícitamente, se infiere del contexto o se utiliza el tipo predeterminado. [51]f64
Los tipos enteros en Rust se nombran en función de su signo y la cantidad de bits que ocupa el tipo. Por ejemplo, i32
es un entero con signo que ocupa 32 bits de almacenamiento, mientras que u8
no tiene signo y solo ocupa 8 bits de almacenamiento. isize
y usize
ocupan espacio de almacenamiento según la arquitectura de la computadora que ejecuta el código; por ejemplo, en computadoras con arquitecturas de 32 bits , ambos tipos ocuparán 32 bits de espacio.
De manera predeterminada, los literales enteros están en base 10, pero se admiten diferentes bases0b11
con prefijos, por ejemplo, para números binarios , 0o567
para octales y 0xDB
para hexadecimales . De manera predeterminada, los literales enteros tienen como valor predeterminado su tipo. Se pueden usar i32
sufijos como para establecer explícitamente el tipo de un literal. [52] Los literales de byte como están disponibles para representar el valor ASCII (en ) de un carácter específico. [53]4u32
b'X'
u8
El tipo booleano se conoce como bool
que puede tomar un valor de true
o false
. A char
ocupa 32 bits de espacio y representa un valor escalar Unicode: un punto de código Unicode que no es un sustituto . [54] Los números de punto flotante IEEE 754 son compatibles con f32
para flotantes de precisión simple y f64
para flotantes de precisión doble . [55]
Los tipos definidos por el usuario se crean con las palabras clave struct
or enum
. La struct
palabra clave se utiliza para indicar un tipo de registro que agrupa múltiples valores relacionados. [56] enum
Los s pueden 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 funcional. [57] Tanto las estructuras como las enumeraciones pueden contener campos con diferentes tipos. [58] Se pueden definir nombres alternativos para el mismo tipo con la type
palabra clave. [59]
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 una función similar a la de las clases en otros lenguajes. [60]
Option
Los valores se manejan utilizando azúcar sintáctica , como la if let
construcción, para acceder al valor interno (en este caso, una cadena): [76]
fn main () { let nombre1 : Option <& str > = None ; // En este caso, no se imprimirá nada si let Some ( nombre ) = nombre1 { println! ( "{nombre}" ); } let name2 : Option <& str > = Some ( "Matthew" ); // En este caso, se imprimirá la palabra "Matthew" si let Some ( name ) = name2 { println! ( "{name}" ); } }
Rust no utiliza punteros nulos para indicar la falta de datos, ya que hacerlo puede provocar una desreferenciación nula . Por consiguiente, se garantiza que las referencias básicas &
y no sean nulas. En cambio, Rust utiliza para este propósito: indica que hay un valor presente y es análogo al puntero nulo. [77] implementa una "optimización de puntero nulo", evitando cualquier sobrecarga espacial para tipos que no pueden tener un valor nulo (referencias o los tipos, por ejemplo). [78]&mut
Option
Some(T)
None
Option
NonZero
A diferencia de las referencias, los tipos de punteros sin formato *const
y *mut
pueden ser nulos; sin embargo, es imposible desreferenciarlos 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 está permitida dentro del código seguro de Rust. [79]
Rust no ofrece conversión de tipos implícita (coerción) entre tipos primitivos. Sin embargo, se puede realizar una conversión de tipos explícita (casting) utilizando la as
palabra clave. [80]
sea x = 1000 ; println! ( "1000 como u16 es: {}" , x como u16 );
El sistema de propiedad de Rust consiste en reglas que garantizan la seguridad de la memoria sin usar un recolector de basura. En tiempo de compilación, cada valor debe estar asociado a una variable llamada el propietario de ese valor, y cada valor debe tener exactamente un propietario. [81] Los valores se mueven entre diferentes propietarios a través de la asignación o pasando un valor como un parámetro de función. Los valores también pueden tomarse prestados, lo que significa que se pasan temporalmente a una función diferente antes de ser devueltos al propietario. [82] Con estas reglas, Rust puede evitar la creación y el uso de punteros colgantes : [82] [83]
fn print_string ( s : Cadena ) { println! ( "{}" , s ); } fn main () { let s = String :: from ( "Hola, Mundo" ); print_string ( s ); // s consumido por print_string // s se ha movido, por lo que ya no se puede usar // otro print_string(s); generaría 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. [84]
Cuando un valor queda fuera del ámbito, se elimina ejecutando su destructor . El destructor puede definirse 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 con ellos se cierran o liberan automáticamente. [85]
Los tiempos de vida suelen ser una parte implícita de todos los tipos de referencia en Rust. Cada tiempo de vida abarca un conjunto de ubicaciones en el código para las que una variable es válida. Por ejemplo, una referencia a una variable local tiene un tiempo de vida correspondiente al bloque en el que está definida: [86]
fn main () { let x = 5 ; // ------------------+- Tiempo de vida 'a // | let r = & x ; // -+-- Tiempo de vida 'b | // | | println! ( "r: {}" , r ); // | | // | | // -+ | } // ------------------+
El verificador de préstamos del compilador Rust utiliza duraciones de vida para garantizar que los valores a los que apunta una referencia sigan siendo válidos. [87] [88] En el ejemplo anterior, almacenar una referencia a una variable x
en r
es válido, ya que variable x
tiene una duración de vida más larga ( 'a
) que variable r
( 'b
). Sin embargo, cuando x
tiene una duración de vida más corta, el verificador de préstamos rechazaría el programa:
fn main () { let r ; // ------------------+- Tiempo de vida 'a // | { // | let x = 5 ; // -+-- Tiempo de vida 'b | r = & x ; // | | } // -| | // | println! ( "r: {}" , r ); // | } // ------------------+
Dado que el tiempo de vida de la variable referenciada ( 'b
) es más corto que el tiempo de vida de la variable que contiene la referencia ( 'a
), el verificador de préstamos genera errores, lo que impide x
que se utilice desde fuera de su alcance. [89]
Los parámetros de duración de vida hacen explícita la duración de las referencias, por ejemplo, especificando la fuente de una salida: [90]
fn remove_prefix <' a > ( mut original : & ' a str , prefijo : & str ) -> & ' a str { si original . starts_with ( prefijo ) { original = original [ prefijo . len () .. ]; } original }
Con información sobre cómo se relacionan los tiempos de vida del valor de salida de la función con sus entradas, el compilador puede evitar problemas de seguridad de memoria, como punteros colgantes.
Cuando los tipos definidos por el usuario contienen referencias a datos, también necesitan utilizar parámetros de duración de vida. El ejemplo siguiente analiza algunas opciones de configuración de una cadena y crea una estructura que contiene las opciones. La función parse_config
también muestra la elisión de duración de vida, lo que reduce la necesidad de definir explícitamente los parámetros de duración de vida. [91]
utilizar std :: colecciones :: HashMap ; // Esta estructura tiene un parámetro de duración, 'src'. El nombre solo se utiliza dentro de la definición de la estructura. #[derive(Debug)] struct Config <' src > { hostname : & ' src str , username : & ' src str , } // El parámetro '_ lifetime, en este caso, se refiere al tiempo de vida anónimo adjunto al tipo // del argumento `config`. fn parse_config ( config : & str ) -> Config <' _ > { let key_values : HashMap < _ , _ > = config . lines () . filter ( | line | ! line . starts_with ( '#' )) . filter_map ( | line | line . split_once ( '=' )) . map ( | ( key , value ) | ( key . trim (), value . trim ()))) . collect (); Config { hostname : key_values [ "hostname" ], username : key_values [ "username" ], } } fn main () { let config = parse_config ( r#"nombre_host = foobar nombre_usuario=barfoo"# , ); println! ( "Configuración analizada: {:#?}" , config ); }
Las características más avanzadas de Rust incluyen el uso de funciones genéricas . A una función genérica se le asignan parámetros genéricos , que permiten que la misma función se aplique a diferentes tipos de variables. Esta capacidad reduce el código duplicado [92] y se conoce como polimorfismo paramétrico .
El siguiente programa calcula la suma de dos cosas, para lo cual la adición se implementa utilizando una función genérica:
utilizar std :: ops :: Add ; // 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 adición devuelve otro T { num1 + num2 // num1 + num2 es una sintaxis simplificada para num1.add(num2) proporcionada por el rasgo Agregar } fn main () { let resultado1 = suma ( 10 , 20 ); println! ( "La suma es: {}" , resultado1 ); // La suma es: 30 deje que result2 = suma ( 10.23 , 20.45 ); println! ( "La suma es: {}" , result2 ); // La suma es: 30.68 }
En el momento de la compilación, sum
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 números flotantes.
Los genéricos se pueden utilizar en funciones para permitir la implementación de 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. [93]
El sistema de tipos de Rust admite un mecanismo llamado rasgos, inspirado en las clases de tipos del lenguaje Haskell , [6] para definir un comportamiento compartido entre diferentes tipos. Por ejemplo, el Add
rasgo se puede implementar para números flotantes y enteros, que se pueden sumar; y el rasgo Display
o Debug
se puede implementar para cualquier tipo que se pueda convertir en una cadena. Los rasgos se pueden usar para proporcionar un conjunto de comportamiento común para diferentes tipos sin conocer el tipo real. Esta función se conoce como polimorfismo ad hoc .
Las funciones genéricas pueden restringir el tipo genérico para implementar una característica o características particulares; por ejemplo, una add_one
función puede requerir que el tipo implemente Add
. Esto significa que una función genérica puede ser verificada por tipo 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 tipo que se usa típicamente en Java y Haskell. El borrado de tipo también está disponible a través de la palabra clave dyn
(abreviatura de dinámico). [94] Debido a que la monomorfización duplica el código para cada tipo usado, puede resultar en 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. [95]
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. [60] Los rasgos pueden proporcionar métodos derivados adicionales cuando se implementan. [96] Por ejemplo, el rasgo Iterator
requiere que el next
método se defina para el tipo. Una vez que next
se define el método, el rasgo puede proporcionar métodos auxiliares funcionales comunes sobre el iterador, como map
o filter
. [97]
Los rasgos de Rust se implementan utilizando un envío 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 tipado de pato ). [98] Los objetos de rasgo enviados dinámicamente se declaran utilizando la sintaxis dyn Tr
donde Tr
es un rasgo. Los objetos de rasgo tienen un tamaño dinámico, por lo tanto, deben colocarse detrás de un puntero, como Box
. [99] El siguiente ejemplo crea una lista de objetos donde cada objeto se puede imprimir utilizando el Display
rasgo:
utilizar std :: fmt :: Mostrar ; sea v : Vec < Caja < dyn Pantalla >> = vec! [ Caja :: new ( 3 ), Caja :: new ( 5.0 ), Caja :: new ( "hola" ), ]; para x en v { println! ( "{x}" ); }
Si un elemento de la lista no implementa la Display
característica, provocará un error en tiempo de compilación. [100]
Rust está diseñado para ser seguro para la memoria . No permite punteros nulos, punteros colgantes o carreras de datos . [101] [102] [103] Los valores de datos se pueden inicializar solo a través de un conjunto fijo de formularios, todos los cuales requieren que sus entradas ya estén inicializadas. [104]
El código inseguro puede subvertir algunas de estas restricciones, utilizando la unsafe
palabra clave. [79] El código inseguro también puede usarse para funcionalidad de bajo nivel, como acceso a memoria volátil , intrínsecos específicos de la arquitectura, juegos de palabras de tipos y ensamblaje en línea. [105]
Rust no utiliza recolección de basura . La memoria y otros recursos se gestionan mediante la convención "la adquisición de recursos es inicialización", [106] con conteo de referencias opcional . Rust proporciona una gestión determinista de los recursos, con una sobrecarga muy baja . [107] Los valores se asignan en la pila de forma predeterminada, y todas las asignaciones dinámicas deben ser explícitas. [108]
Los tipos de referencia integrados que utilizan el &
símbolo no implican un recuento de referencias en tiempo de ejecución. La seguridad y validez de los punteros subyacentes se verifica en tiempo de compilación, lo que evita punteros colgantes y otras formas de comportamiento indefinido . [109] El sistema de tipos de Rust separa las referencias compartidas e inmutables de la forma &T
de las referencias únicas y mutables de la forma &mut T
. Una referencia mutable se puede convertir en una referencia inmutable, pero no al revés. [110]
Es posible ampliar el lenguaje Rust usando macros.
Una macro declarativa (también llamada "macro por ejemplo") es una macro, definida usando la macro_rules!
palabra clave, que utiliza la coincidencia de patrones para determinar su expansión. [111] [112] Un ejemplo es la println!()
macro. [113]
Las macros procedimentales son funciones de Rust que ejecutan y modifican el flujo de tokens de entrada del compilador antes de que se compilen otros componentes. Por lo general, son más flexibles que las macros declarativas, pero son más difíciles de mantener debido a su complejidad. [114] [115]
Las macros de procedimiento vienen en tres versiones:
custom!(...)
#[derive(CustomDerive)]
#[custom_attribute]
La rsx!
macro en el marco de interfaz Dioxus es un ejemplo de una macro similar a una función. [116] [117] La serde_derive
macro [118] 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 lenguaje, como la extendr
biblioteca para enlaces de Rust a R. [119]
El siguiente código muestra el uso de las macros de procedimiento derivadas de Serialize
, Deserialize
, y Debug
para implementar la lectura y escritura de JSON, así como la capacidad de formatear una estructura para la depuración.
utilizar serde :: { Serializar , Deserializar }; #[derive(Serializar, Deserializar, Depurar)] struct Point { x : i32 , y : i32 , } fn main () { let punto = Punto { x : 1 , y : 2 }; deje que serializado = serde_json :: to_string ( & point ) .unwrap (); println! ( "serializado = {}" , serializado ); deje que deserialice : Punto = serde_json :: from_str ( & serializado ) .unwrap (); println! ( "deserializado = {:?}" , deserializado ); }
Rust no admite argumentos variádicos en funciones. En su lugar, utiliza macros . [120]
macro_rules! calculate { // El patrón para un solo `eval` ( eval $e : expr ) => {{ { let val : usize = $e ; // Fuerza a que los tipos sean enteros println! ( "{} = {}" , stringify! { $e }, val ); } }}; // Descomponer múltiples `eval`s recursivamente ( eval $e : expr , $( eval $es : expr ), + ) => {{ calculate ! { eval $e } calculate ! { $( eval $es ), + } }}; } fn main () { calcular ! { // Mira ma! Variadic `calculate!`! eval 1 + 2 , eval 3 + 4 , eval ( 2 * 3 ) + 1 } }
c_variadic
conmutador de características. Al igual que con otras interfaces de C, el sistema se considera unsafe
parte de Rust. [121]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 Rust desde esos lenguajes. A partir de 2024 [actualizar], existe una biblioteca externa llamada CXX para llamar a o desde C++. [122] Rust y C difieren en cómo disponen las estructuras en la memoria, por lo que a las estructuras de Rust se les puede dar un #[repr(C)]
atributo, lo que obliga a tener el mismo diseño que la estructura C equivalente. [123]
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 Rust rustup
, un instalador de la cadena de herramientas de Rust desarrollado por el proyecto Rust. [124]
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, LLVM se invoca como un subcomponente para traducir el código IR a código de máquina . Luego, se utiliza un enlazador para combinar múltiples paquetes juntos como un solo archivo ejecutable o binario. [125] [126]
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. [127] La intención de esos backends alternativos es aumentar la cobertura de la plataforma de Rust o mejorar los tiempos de compilación. [128] [129]
La biblioteca estándar de Rust define e implementa muchos tipos de datos personalizados ampliamente utilizados, incluidas las estructuras de datos centrales como Vec
, Option
, y HashMap
, así como los tipos de puntero inteligente . Rust también proporciona una forma de excluir la mayor parte de la biblioteca estándar utilizando el atributo #![no_std]
; esto permite que las aplicaciones, como los dispositivos integrados, eliminen el código de dependencia o proporcionen sus propias estructuras de datos centrales. Internamente, la biblioteca estándar se divide en tres partes, core
, alloc
, y std
, donde std
y alloc
se excluyen mediante #![no_std]
. [130]
Cargo es el sistema de compilación y el administrador de paquetes de Rust . Descarga, compila, distribuye y carga paquetes (llamados crates ) que se mantienen en un registro oficial. También actúa como interfaz para Clippy y otros componentes de Rust. [131]
De manera predeterminada, Cargo obtiene sus dependencias del registro contribuido por el usuario crates.io , pero los repositorios de Git y los cajones en el sistema de archivos local y otras fuentes externas también se pueden especificar como dependencias. [132]
Rustfmt es un formateador de código para Rust. Formatea los espacios en blanco y las sangrías 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. [133]
Clippy es la herramienta de análisis de errores integrada de Rust para mejorar la corrección, el rendimiento y la legibilidad del código de Rust. A partir de 2024 [actualizar], tiene más de 700 reglas. [134] [135]
Después de Rust 1.0, se desarrollan nuevas características en versiones nocturnas que se lanzan diariamente. Durante cada ciclo de lanzamiento de seis semanas, los cambios de 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. [136]
Cada dos o tres años se produce una nueva "edición". Las ediciones se lanzan para permitir la realización de cambios limitados , como la promoción await
a una palabra clave para admitir funciones async/await . Los crates que apuntan a diferentes ediciones pueden interoperar entre sí, por lo que un crate puede actualizarse a una nueva edición incluso si sus llamadores o sus dependencias aún apuntan a ediciones anteriores. La migración a una nueva edición puede ser asistida con herramientas automatizadas. [137]
rust-analyzer es una colección de utilidades que proporciona a los entornos de desarrollo integrados (IDE) y editores de texto información sobre un proyecto Rust a través del Protocolo de servidor de lenguaje . Esto permite funciones como el autocompletado y la visualización de errores de compilación durante la edición. [138]
En general, las garantías de seguridad de memoria de Rust no imponen una sobrecarga en tiempo de ejecución. [139] 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. [140] Dado que no realiza la recolección de basura, Rust suele ser más rápido que otros lenguajes seguros para la memoria. [141] [84] [142]
Rust ofrece dos "modos": seguro e inseguro. El modo seguro es el "normal", en el que se escribe la mayor parte de Rust. En el modo inseguro, el desarrollador es responsable de la seguridad de la memoria del código, que es utilizado por los desarrolladores en casos en los que el compilador es demasiado restrictivo. [143]
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. [144] El sistema de propiedad y préstamo permite implementaciones de copia cero para algunas tareas sensibles al rendimiento, como el análisis . [145] 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. [146] El compilador también utiliza la expansión en línea para eliminar las llamadas a funciones y las invocaciones de métodos enviados estáticamente. [147]
Dado que Rust utiliza LLVM , cualquier mejora de rendimiento en LLVM también se traslada a Rust. [148] A diferencia de C y C++, Rust permite reordenar elementos de estructura y enumeración [149] 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 memoria caché . [150]
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 . [151] Los componentes del motor Servo se incorporaron más tarde al motor de navegador Gecko subyacente a Firefox. [152] En enero de 2023, Google ( Alphabet ) anunció el soporte para el uso de bibliotecas de terceros Rust en Chromium . [153] [154]
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. [155] [156] Amazon Web Services utiliza Rust en "componentes sensibles al rendimiento" de sus diversos servicios. En 2019, AWS publicó en código abierto Firecracker , una solución de virtualización escrita principalmente en Rust. [157] Microsoft Azure IoT Edge, una plataforma utilizada para ejecutar servicios de Azure en dispositivos IoT , tiene componentes implementados en Rust. [158] Microsoft también utiliza Rust para ejecutar módulos en contenedores con WebAssembly y Kubernetes . [159] Cloudflare , una empresa que proporciona servicios de red de distribución de contenido , utilizó Rust para crear un nuevo proxy web llamado Pingora para aumentar el rendimiento y la eficiencia. [160] El administrador de paquetes npm comenzó a utilizar Rust para su servicio de autenticación de producción en 2019. [161] [162] [163]
En sistemas operativos, los desarrolladores de Android estaban usando Rust en 2021 para reescribir componentes existentes. [164] [165] El proyecto Rust para Linux , lanzado en 2020, agregó soporte inicial de Rust a Linux a fines de 2022, y los primeros controladores de Linux escritos en Rust se lanzaron a fines de 2023. [166] [167] Microsoft está reescribiendo partes de Windows en Rust. [168] El proyecto r9 tiene como objetivo volver a implementar Plan 9 de Bell Labs en Rust. [169] Rust se ha utilizado en el desarrollo de nuevos sistemas operativos como Redox , un sistema operativo "similar a Unix" y microkernel , [170] Theseus, un sistema operativo experimental con gestión de estado modular, [171] [172] y la mayor parte de Fuchsia . [173] Rust también se utiliza para herramientas de línea de comandos y componentes del sistema operativo, incluido stratisd , un administrador de sistema de archivos [174] [175] y COSMIC, un entorno de escritorio de System76 . [176]
En el desarrollo web, Deno , un entorno de ejecución seguro para JavaScript y TypeScript , está construido sobre V8 usando Rust y Tokio. [177] Otras adopciones notables en este espacio incluyen Ruffle , un emulador SWF de código abierto , [178] y Polkadot , una plataforma de cadena de bloques y criptomonedas de código abierto . [179]
Discord , una empresa de software de mensajería instantánea , ha reescrito partes de su sistema en Rust para aumentar el rendimiento en 2020. Ese mismo año, Dropbox anunció que su sincronización de archivos había sido reescrita en Rust. Facebook ( Meta ) también ha utilizado Rust para rediseñar su sistema que administra el código fuente de proyectos internos. [16]
En la encuesta de desarrolladores de Stack Overflow de 2023 , el 13 % de los encuestados había realizado recientemente un desarrollo extenso en Rust. [180] La encuesta también nombró a Rust como el "lenguaje de programación más amado" todos los años desde 2016 hasta 2023 (inclusive) y el "lenguaje de programación más admirado" en 2024, según la cantidad de desarrolladores interesados en seguir trabajando en el mismo lenguaje. [181] [nota 5] En 2023, Rust fue la sexta "tecnología más buscada", con un 31 % de desarrolladores que actualmente no trabajan en Rust expresando interés en hacerlo. [180]
Rust ha sido estudiado en la investigación académica, tanto por las propiedades del lenguaje en sí como por la utilidad que proporciona para escribir software utilizado en la investigación. Se han examinado sus características en cuanto a seguridad [182] [143] y rendimiento [183] .
En un artículo de revista publicado en Proceedings of the International Astronomical Union , los astrofísicos Blanco-Cuaresma y Bolmont reimplementaron programas responsables de simular sistemas multiplanetarios en Rust, y descubrieron que era un lenguaje de programación competitivo por su "velocidad y precisión". [14] Asimismo, un artículo publicado en Nature compartió varias historias de bioinformáticos que usan Rust por su rendimiento y seguridad. [131] Sin embargo, ambos artículos han citado los conceptos únicos de Rust, incluido su sistema de propiedad, que es difícil de aprender como uno de los principales inconvenientes para adoptar Rust.
Rust se ha destacado por tener una comunidad inclusiva y, en particular, por acoger a personas de la comunidad queer , en parte debido a su código de conducta , que describía un conjunto de expectativas que los miembros de la comunidad Rust debían seguir. Un artículo de MIT Technology Review describió a la comunidad Rust como "inusualmente amigable" con los recién llegados. [16] [131]
La Fundación Rust es una organización de membresía sin fines de lucro incorporada en Estados Unidos , con los propósitos principales de respaldar el proyecto técnico como entidad legal y ayudar a administrar la marca registrada y los activos de infraestructura. [186] [35]
Se estableció el 8 de febrero de 2021, con cinco miembros corporativos fundadores (Amazon Web Services, Huawei, Google, Microsoft y Mozilla). [187] La junta directiva de la fundación está presidida por Shane Miller. [188] A partir de fines de 2021, su directora ejecutiva y directora ejecutiva es Rebecca Rumbul. [189] Antes de esto, Ashley Williams fue directora ejecutiva interina. [35]
El proyecto Rust está compuesto por equipos que son responsables de diferentes subáreas del desarrollo. El equipo de compiladores desarrolla, administra y optimiza los componentes internos del compilador; y el equipo de lenguaje diseña nuevas características del lenguaje y ayuda a implementarlas. El sitio web del proyecto Rust enumera 6 equipos de alto nivel a partir de julio de 2024. [actualizar][ 190] Los representantes de los equipos forman el consejo de liderazgo, que supervisa el proyecto Rust en su conjunto. [191]
str
y String
son siempre cadenas UTF-8 válidas y pueden contener ceros internos.Quienes estén familiarizados con el estilo Elm pueden reconocer que los mensajes
--explain
actualizados se inspiran en gran medida en el enfoque Elm.
Están inspirados en... tipos de propiedad y punteros prestados en el lenguaje de programación Rust.
Ambos son lenguajes con llaves, con una sintaxis similar a C que los hace poco intimidantes para los programadores de C.
Observamos una gran variación en los costos generales de la indexación marcada: el 23,6% de los puntos de referencia informan impactos significativos en el rendimiento de la indexación marcada, pero el 64,5% informa poco o ningún impacto y, sorprendentemente, el 11,8% informa una mejora en el rendimiento ... En última instancia, si bien la indexación sin marcar puede mejorar el rendimiento, la mayoría de las veces no lo hace.
... Si bien algunos compiladores (por ejemplo, Rust) admiten la reordenación de estructuras [82], los compiladores de C y C++ tienen prohibido reordenar las estructuras de datos (por ejemplo, struct o class) [74] ...