Guía para aplanar la curva de aprendizaje de Rust
(corrode.dev)- Rust es un lenguaje que exige una forma de pensar completamente nueva, y la mentalidad influye mucho en la velocidad de aprendizaje
- La clave es hacerse amigo del compilador; no basta con corregir los mensajes de error, también es importante entender la razón detrás de ellos
- Al principio conviene usar activamente
clone()yunwrappara empezar en pequeño y refactorizar gradualmente - Hay que escribir mucho código a mano y acumular intuición y memoria muscular a través de errores y prueba y error
- Rust tiene una filosofía de desarrollo centrada en el sistema de tipos, por lo que hace falta leer bien la documentación y practicar cómo modelar con tipos
Flattening Rust's Learning Curve
Rust es conocido como un lenguaje difícil de aprender, pero este texto presenta consejos concretos, basados en la experiencia del autor, sobre la actitud y el enfoque para aprender Rust de forma más efectiva
Baja la guardia
- Rust exige un modelo mental diferente al de otros lenguajes
- A veces un principiante puede aprender más rápido que un experto → la actitud y la apertura mental son clave
- En vez de ver al borrow checker como un enemigo, hay que tratarlo como un coautor y esforzarse por entender sus mensajes de error
- Es importante intentar comprender a fondo por qué el compilador pide parámetros de lifetime
- Si el código se ve feo y complejo, puede ser señal de un mal diseño; hay que usarlo como impulso para buscar una mejor solución
- La verbosidad de Rust ayuda en aplicaciones grandes y facilita la refactorización
- Conviene activar y aprovechar todos los lint de
clippydesde el principio
Pasos pequeños
- Al inicio se puede usar sin problema
String,clone(),unwrapy refactorizar después - Es mejor empezar con
ifymatchsimples antes que con cadenas complejas de métodos - Conviene evitar async durante la primera semana
- Aprende probando fragmentos pequeños de código en Rust Playground
- Practica con un archivo
main.rspor concepto, y escribe la mayor parte del código con la idea de que luego lo vas a desechar
Sé preciso
- En Rust, la precisión es una condición de supervivencia
- Un typo o un error pequeño puede convertirse de inmediato en un error de compilación, así que hay que tener cuidado
- Ayuda desarrollar el hábito de agregar automáticamente
&,mutcuando corresponde - Los videos en vivo de desarrolladores como
Tsodingson una buena referencia
No hagas trampa
- Depender de LLM o del autocompletado de código ralentiza el aprendizaje
- Hace falta teclear por tu cuenta y, cuando algo no se entienda, buscarlo en la documentación
- No programes en piloto automático
- Acepta los errores y úsalo para entender cómo funciona el compilador
- También se recomienda practicar predecir si el código va a compilar antes de ejecutarlo
- Es importante desarrollar el hábito de leer y analizar código de otras personas
- Mientras aprendes, conviene evitar crates externos, con excepciones como
serdeyanyhow
Construye buenas intuiciones
- Los conceptos de
lifetimeyownershipse entienden mejor de forma visual - Se recomienda usar herramientas como
excalidrawpara dibujar el flujo de datos y la arquitectura - Muchos ingenieros y matemáticos brillantes también aprovechan muy bien las herramientas de visualización
Construye sobre lo que ya sabes
- En Rust, incluso conceptos familiares funcionan de manera distinta (por ejemplo
muto el movimiento de valores) - Aun así, aprender comparando con otros lenguajes sigue siendo útil
Ejemplos:
- Trait → se parece a una Interface, pero no es lo mismo
- Struct → se siente como una clase sin herencia
- Closure → similar a una lambda
- Module → namespace
- Borrow → puntero con propietario único
- Option → Maybe monad
- Enum → Algebraic data type
- Rosetta Code es útil para aprender comparando implementaciones entre varios lenguajes
- También es efectivo aprender porteando a Rust código de un lenguaje que ya conoces
- Vale la pena pensar cómo expresar en Rust los modismos de otros lenguajes, como sintaxis integrada para listas o ciertos estilos de bucles
No adivines
- Rust es un lenguaje en el que adivinar no funciona
- Vale la pena preguntarse por qué hace falta escribir código como
"hello".to_string() - Los mensajes de error son muy valiosos, así que nunca hay que ignorar las pistas que esconden
- En especial, los errores relacionados con el borrow checker deben analizarse siguiendo a mano el flujo de datos
Apóyate en el desarrollo guiado por tipos
- Rust es un lenguaje centrado en el sistema de tipos
- Se puede obtener muchísima información de las firmas de funciones y las definiciones de tipos
- Conviene leer con frecuencia la documentación y el código fuente de la biblioteca estándar
- Diseñar primero los tipos y luego escribir el código permite crear estructuras más precisas y reutilizables
- Si expresas una invariante mediante tipos, el código incorrecto ni siquiera va a compilar
Invierte tiempo en encontrar buenos recursos de aprendizaje
- Todavía no hay tantos materiales para aprender Rust, así que encontrar desde temprano recursos que encajen contigo te puede ahorrar mucho tiempo
- Herramientas de aprendizaje como
Rustlingsgeneran opiniones divididas según el estilo de cada quien - Puede que materiales orientados a resolver problemas, como
Advent of CodeoProject Euler, te resulten más adecuados - Los videos de YouTube conviene usarlos más como entretenimiento que como fuente principal de información
- La forma más efectiva es comprar un libro, leerlo fuera de línea y escribir el código con tus propias manos
- Si es posible, recibir formación o mentoría de un experto también puede ahorrar muchísimo tiempo a largo plazo
Encuentra un compañero de programación
- También ayuda seguir de cerca y observar el código de un colega con más experiencia
- Puedes mejorar pidiendo revisiones de código o revisando código ajeno en foros de Rust, Mastodon y otros espacios
Explica código Rust a desarrolladores que no usan Rust
- Intentar explicárselo a alguien que no conoce Rust también es una forma efectiva de aprender
- También se recomienda contribuir a código sin mantenimiento en proyectos de código abierto
- Es útil crear un glosario que conecte la terminología de Rust con el lenguaje del dominio de tu trabajo
Cree en el beneficio a largo plazo
- Rust es un lenguaje pensado más para la calidad a largo plazo que para la productividad inmediata
- Es difícil volverse experto de la noche a la mañana, pero con un mes de enfoque intenso se puede ganar mucho
- Rust es un “lenguaje del día 2”: el primer día es duro, pero su valor crece si lo usas de forma continua
- Para tener éxito, no basta con aprenderlo para el currículum; también hay que disfrutar la programación en sí
4 comentarios
Antes, con fines de aprendizaje, intenté reescribir en Rust un código que había escrito previamente en C, y recuerdo que fue una experiencia muy frustrante manejar punteros... Todavía no termino de ordenar en mi cabeza cómo funcionan cosas como
RcoRefCell...Después de leer una sola vez la documentación básica y el Nomicon en orden, nunca me volví a trabar con Rust, así que me pregunto si de verdad su curva de aprendizaje es tan alta.
Uf, si te acostumbras a
unwrapyclone, después vas a sufrir muchísimo más adelante por el tema de la propiedad ;_;Opinión de Hacker News
Se siente como leer “A Discipline of Programming”; la forma moralizante de explicar de Dijkstra era algo que antes sí hacía falta porque la gente ni siquiera entendía bien los conceptos de programación. Las explicaciones de ownership en Rust suelen ser demasiado verbosas; casi siempre contienen las ideas clave, pero quedan escondidas debajo de los ejemplos. En Rust, cada objeto de datos tiene exactamente un dueño. Esa propiedad puede transferirse, pero siempre para que siga habiendo un solo dueño. Si se necesitan varios dueños, entonces el verdadero dueño debe ser una celda con conteo de referencias. Esa celda se puede clonar. Cuando el dueño desaparece, también desaparece lo que poseía. Se puede usar una referencia (
ref) para pedir prestado temporalmente el acceso a un objeto de datos. Propiedad y referencia son cosas claramente distintas. Las referencias se pueden pasar y almacenar, pero no pueden sobrevivir más que el objeto al que apuntan. Si no, ocurre un error de “puntero colgante”. Estas reglas son aplicadas estrictamente en tiempo de compilación por el borrow checker. Ese es el modelo de ownership de Rust; una vez que lo entiendes, los detalles al final se reducen a esas reglas.No sé si solo me pasa a mí, pero me cuesta seguir explicaciones conceptuales de este tipo; me pasó lo mismo con encapsulación. Solo dicen que “se oculta información”, pero no profundizan en cómo o por qué concretamente. Por ejemplo, no entiendo quién es exactamente el dueño en Rust. ¿Es el stack frame? Por la estructura LIFO, me pregunto por qué hay que transferir ownership al callee; como la pila del callee desaparece primero, parecería que no hay riesgo. Si es por optimización, ¿es para poder liberar el objeto antes? Y si el dueño no es el stack frame, tampoco sé qué sería entonces. También me confunde por qué una referencia mutable solo se puede dar una vez. En un entorno de un solo hilo, de todos modos una función no empieza antes de que termine otra, así que parecería que no habría problema con que ambas reciban una referencia mutable. Si el problema solo aparece en entornos multihilo, me pregunto si no bastaría con dar error ahí. Por dudas como estas, cada vez que intento estudiar Rust termino abandonándolo.
Esto no es una explicación de ownership sino una explicación de motivación. La parte más difícil y central es cómo leer firmas de funciones cuando los lifetimes están enredados de forma compleja, y cómo entender y corregir los errores del compilador que aparecen al llamar ese tipo de funciones.
Hacer un resumen que parezca correcto y perfecto para quienes ya conocen estos conceptos es mucho más fácil que explicárselos a alguien que los está aprendiendo por primera vez. Si se explicara así, ¿alguien que solo conoce call-by-sharing lo entendería de inmediato? Yo diría que no.
Si alguien que no conoce Rust leyera solo este resumen, no aprendería nada sobre Rust. Solo se quedaría con la impresión de que “este lenguaje parece tener magia negra en el compilador”.
Los conceptos de ownership y borrowing en sí son fáciles de entender. La verdadera razón por la que Rust es difícil de aprender es que la firma de la función y el código real de uso tienen que demostrar mutuamente que la referencia no vive más que el objeto. Como referencia adicional, no guardar objetos referenciados dentro de tipos hace que la prueba sea menos compleja, así que suele ser una mejor elección.
Desde hace tiempo buscaba textos sobre cómo la gente en los 60 manejaba el estado de sistemas y aplicaciones a nivel ensamblador. Había oído que el paper de Sketchpad de Sutherland tiene mucho detalle sobre estructuras de datos, pero solo leí los capítulos 2 y 3.
Esta explicación no me dice mucho. No define con claridad ownership ni borrowing. Ambos parecen términos basados en una metáfora de gestión de activos financieros. No conozco bien Rust, pero siento que esa elección de palabras ya hace más difícil entender el concepto. Las metáforas suelen ser un arma de doble filo. Creo que ayudaría más explicarlo con términos más directos relacionados con memoria.
En la explicación del modelo se omitió por completo la importante diferencia entre préstamos exclusivos/compartidos (o mutables/inmutables). Rust tomó muchas decisiones sobre cómo permitir ese tipo de préstamos, y eso no es intuitivo. Por ejemplo, la regla de no aliasing no viene de la intuición, sino de objetivos de optimización de funciones. Lo más complicado del borrowing es que, por las reglas de elisión de lifetimes, los mensajes de error del compilador a veces apuntan a un lugar distinto de la causa real. Esas reglas de elisión no son intuitivas y fueron decisiones introducidas para simplificar.
La versión revisada del Rust Book hecha por Brown University explica realmente muy bien el borrow checker.
La explicación parece incompleta; por ejemplo, no dice qué pasa cuando desaparece la parte que pidió prestado.
Dice “el verdadero dueño debe ser una celda con conteo de referencias”, pero parece explicado de un modo que solo entenderá quien ya sabe qué es eso.
Solo entendí de verdad el borrow checker después de intentar implementar mi propio borrow checker.
El segundo punto de la segunda sección está gravemente exagerado. En la práctica existen muchísimos casos de código seguro que Rust no compila. Esta complejidad apareció para dejar claros los límites de lo que el compilador puede demostrar.
El modelo de ownership de Rust, los lifetimes, los
enumy el pattern matching me resultaron muy pesados al principio. En mi primer intento me sentí abrumado demasiado rápido, y en el segundo traté de leer cada línea del libro, pero se me acabó la paciencia. Aun así, me di cuenta de que Rust es un lenguaje que da una comprensión más profunda de la programación y del diseño de software. No fue hasta el tercer intento que por fin empecé a aprender reescribiendo en estilo Rust pequeños programas y scripts que antes había hecho, y en ese proceso también fui incorporando el manejo de errores al estilo Rust, la representación de datos aprovechando los tipos y el pattern matching. Después de esa experiencia, estoy convencido de que aprender Rust fue una de las mejores decisiones de mi vida como programador. Definir tipos, structs yenumpor adelantado, y luego escribir funciones basadas en datos inmutables y pattern matching, es algo que ahora aplico naturalmente incluso en otros lenguajes.Tuve una experiencia parecida. Recién en mi tercer intento de aprender Rust empecé a agarrarle bien la mano y pude escribir algunos programas de forma efectiva. Incluso con muchos años de experiencia programando, a veces hace falta estudiar algo varias veces. Antes me pasó lo mismo para entender Dagger, el framework de inyección de dependencias del JVM: necesité exactamente tres intentos. Quizá sea un patrón común en mí al aprender cosas complejas.
Es algo que se ve mucho cuando desarrolladores de C++ llegan por primera vez a Rust: si lo abordan con ideas de C++, terminan peleándose todo el tiempo con el “borrow checker”. Cuando por fin interiorizas bien las convenciones de Rust, luego puedes llevar esas mismas convenciones de vuelta a C++ y escribir código más sólido, aunque C++ no tenga borrow checker.
El consejo de “leer cuidadosamente todo el código antes de compilar y corregir los errores de dedo” me resulta raro. El compilador de Rust es famoso por sus mensajes de error muy amables, así que no veo por qué tendría que quedarme buscando errores de dedo por mi cuenta; quiero que la computadora me los detecte.
cargo fixcorrige automáticamente algunos problemas, pero no todos.Hay consejos como “no te resistas”, “para aprender hay que dejar de lado la arrogancia”, “hace falta declarar la rendición”, “la resistencia es inútil; si no lo aceptas rápido, solo prolongarás el sufrimiento”, “olvida lo que sabes”, y eso me hace pensar que el SO del telescreen de Orwell debió de estar escrito en Rust.
Rust tiene una barrera de entrada bastante grande para principiantes. Es muy distinto a otros lenguajes —intencionalmente, pero eso mismo lo vuelve difícil de abordar. La sintaxis es compleja y muy condensada, hasta parece que alguien escribió golpeando el teclado con los codos. Un solo carácter puede cambiar por completo el significado, y hay mucho anidamiento. Muchas funciones del lenguaje son difíciles de entender sin una base teórica, así que la complejidad crece todavía más. El sistema de tipos y el mecanismo de borrowing son los ejemplos típicos. Para un usuario común de Python o JavaScript, es prácticamente un idioma alienígena. En una época en la que la mayoría de los programadores en la práctica no tienen una formación de posgrado en CS, Rust no parece muy adecuado. Y encima los macros lo complican todavía más. Si no sabes cómo están definidos, cuesta entender qué significa el código. Últimamente me entusiasma que los LLM puedan bajar esa barrera. Todavía no siento la necesidad de aprender Rust, pero gracias a los LLM quizá algún día vuelva a intentarlo. Rust sí que es un lenguaje excepcionalmente difícil de aprender.
Siento que, en cualquier situación, debe de haber una opción mejor que Rust. Aun así, trato de mantener la mente abierta. Tal vez cuando Rust sea lo bastante común, ahí sí tenga más sentido.
Si hace falta incluso escribir artículos para convencer a la gente de que vale la pena esforzarse en aprender un lenguaje, me hace sospechar que tal vez haya un problema con el diseño del propio lenguaje. (No lo tomen demasiado en serio, porque no he aprendido Rust).
Tu comentario solo se puede leer como “si algo es difícil, no vale la pena”. Todo tiene ventajas y desventajas; me pregunto si para ti el solo hecho de que existan desventajas significa que ni siquiera vale la pena intentarlo. Si alguien escribiera con pasión sobre lo difícil que es tocar el arpa, ¿eso volvería al arpa un mal hobby?
Cuando uno llega a ser desarrollador senior, recién ahí puede que haya visto de reojo las lecciones que Rust considera importantes, pero muchas veces sin haberlas internalizado de verdad. Mucha gente piensa “ya uso un lenguaje con garbage collection, ¿qué me va a enseñar Rust?”. En la práctica, cuando se enredan la mutabilidad y las referencias compartidas, se arma un caos enorme, y por eso terminan apareciendo muchos objetos inmutables. Una vez que tienes objetos inmutables, empiezas a preguntarte cómo transformarlos cómodamente de nuevo, y pueden terminar siendo menos cómodos de usar que los objetos mutables. Cuando intentas expresar que “este objeto tiene momentos en los que puede mutarse y momentos en los que es inmutable”, al final aparece la necesidad de un borrow checker. Una vez que existe un borrow checker, queda la pregunta: “entonces, ¿por qué seguimos necesitando garbage collection?”. Al final se usa garbage collection solo porque da flojera entender con claridad el lifetime de los objetos. Rust te obliga a vivir esa reflexión de fondo de manera directa.
Muchas decisiones de diseño de Rust son difíciles de entender. Mojo también tiene borrow checker, pero es mucho más fácil de aprender que Rust por algunas decisiones concretas. La primera es la semántica de valor. En Rust, a los principiantes se les suele decir que usen
clone()para todo, pero en la mayoría de los lenguajes estáticos (C, C++, Go, etc.) la semántica de valor es lo normal. La segunda es que en Mojo los lifetimes no determinan si un valor puede usarse o no según el scope, sino el momento en que debe eliminarse. Si quedan referencias, el lifetime se extiende, y cuando ya no se usa, se elimina de inmediato. Por eso en Mojo no hace falta ver errores del tipo “el valor no vive lo suficiente”. Solo con esas dos decisiones de diseño ya se reduce bastante la carga.Para un principiante, cualquier lenguaje es difícil de aprender, así que tampoco diría que Rust sea algo tan especial. Programar de por sí ya tiene curva de aprendizaje.
Creo que la existencia de un artículo así dice más sobre el autor que sobre el lenguaje. No lo digo para criticarlo; me parece genial compartir esa pasión de esta manera.
Este artículo parece hablar más de la curva de aprendizaje que de qué problema resuelve Rust. Habría que explicar ambas cosas de forma equilibrada para poder decidir de verdad si vale la pena intentarlo.
Se puede debatir todo lo que se quiera sobre las decisiones de diseño de Rust, pero es difícil juzgar al lenguaje solo porque hagan falta artículos como este. De hecho, yo diría que Python es el lenguaje que más necesita textos de ese tipo. A medida que aumentan los programadores sin formación en ingeniería, Python se vuelve paradójicamente un lenguaje que cualquiera puede usar, mientras que Rust no. Para algunas personas, Rust es mucho más fácil de aprender que C o Zig. También quiero mucho a Python, pero reconozco que en el fondo es un lenguaje terrible. Incluso en la era de los LLM, la gente sigue sin saber mucho sobre cómo escribir Python optimizado. Nuestros amigos de la IA, si no se les indica, seguirán generando puro Python ineficiente.
Ante la pregunta “¿no será un problema de diseño del lenguaje?”, mi respuesta sería “¿por qué?”.
Habiendo aprendido Rust, diría que incluso si uno usa sobre todo Python, nunca he sentido que Rust tenga defectos como lenguaje. Más bien es un lenguaje muy estricto, así que si insistes en escribirlo de una manera que no es rustiana, solo te haces sufrir más. Si trabajas al estilo Rust, cuanto más complejo se vuelve el problema, más ayuda te da. En otros lenguajes los errores se descubren uno por uno en tiempo de ejecución; en Rust, casi todos se detectan en compilación. Claro que no evita los errores lógicos, pero para eso ayuda su fuerte integración con testing. Tiene desventajas, pero Rust definitivamente vale la pena aprenderlo al menos una vez. La forma en que Rust busca reducir errores también se puede aprovechar como buenos hábitos de desarrollo en otros lenguajes.
Es cierto que Rust es tan complejo que a un LLM le cuesta generar código Rust correcto de una sola vez. Aun así, me parece mucho mejor eso que muchos de los problemas de JavaScript u otros lenguajes débiles o dinámicos.
Yo aprendí Rust y coincido contigo. Rust es realmente complejo y se siente como un lenguaje “diseñado por comité”. Tiene tooling excelente, y aun así, aunque es menos complejo que C++, de ninguna manera es un lenguaje fácil de aprender.
El problema con este tipo de artículos es que no llegan al fondo del asunto. Hay programas que Rust simplemente no permite escribir. Hay buenas razones para eso, pero es algo esencialmente distinto de casi todos los lenguajes que la gente ha usado. Como definitivamente existen programas que no se pueden escribir en Rust, hay que aceptar eso; si no, Rust probablemente no sea para ti.
Uno de los enfoques menos comunes al aprender Rust es empezar por dominar solo una parte del lenguaje. Por ejemplo, en mi libro introductorio de Rust no enseño lifetimes para nada. Se pueden escribir programas perfectamente funcionales usando solo funciones sin lifetimes. Con los macros pasa algo parecido: no son fáciles, pero igual conviene aprender primero un subconjunto. Y también creo que, en vez de apoyarse desde el principio únicamente en
copy()oclone(), es más correcto aprender primero el concepto de borrowing. Borrowing es el núcleo del lenguaje.La única forma de que aprenda Rust es que empiecen a aparecer muchos trabajos de más de 300 mil dólares al año. También creo que Rust tiene potencial para reemplazar a C++ en el mundo quant en el futuro. Pero ya existe OCaml, y si voy a tener que aprender un lenguaje extremadamente difícil y complejo, primero quiero ver el dinero. Hasta ahora, los trabajos mejor pagados que he tenido fueron con Python.
Al leer estos comentarios, me doy cuenta de una actitud que suele verse en programadores veteranos cuando sienten que los están corrigiendo. Cuanto más tiempo trabaja alguien, más fácil es que se vuelva terco. Estaría bueno que cada uno se preguntara por qué terminó rechazando las sugerencias del compilador: qué quiere hacer distinto y qué es exactamente lo que se lo está impidiendo.