If you write JavaScript or TypeScript for a living, Rust can look like a language from another planet: lifetimes, borrow checkers, &mut, compiler errors that run for twenty lines. Underneath, though, a lot of it will feel familiar. It has closures, iterators with map and filter, async/await, a package manager that works like npm, and a type system that does what TypeScript's does, then goes further and also proves your program can't touch freed memory or race on shared data.
This guide takes you from zero to the point where you can read real Rust code and write a small program of your own. Each section puts a Rust idea next to the JS/TS one you already know. Every example has line-by-line notes under it: tap or hover a highlighted line to read what it does. The Rust examples are live. Edit them and press Run (or ⌘/Ctrl + Enter) to compile and run them on the official Rust Playground, right here on the page.
Why Rust, for a JS/TS developer
Rust began as a side project of Graydon Hoare at Mozilla in 2006, was sponsored by Mozilla from 2009, and reached 1.0 in May 2015. Since 2021 it has been looked after by the independent Rust Foundation, whose members include AWS, Google, Microsoft and Huawei. Developers have voted it the most admired language in Stack Overflow's survey every year since 2016.
It sets out to give you two things that used to be a trade-off: the speed and control of C and C++, and the memory safety of a garbage-collected language like JavaScript, without the garbage collector. Here is how that changes things compared with what you know:
- No garbage collector, no runtime. V8 pauses now and then to find and free unused objects. Rust frees each value at a point the compiler decides ahead of time, so there are no pauses and memory use stays flat and predictable.
- Types are checked, then compiled away. Like TypeScript, Rust checks types before the program runs. Unlike TypeScript, there is no
any, noas unknown as, and the types decide the machine code that gets generated. - Ownership replaces "everything is a shared reference." In JS, any number of variables can point at the same object and change it. In Rust, each value has one owner, and borrowing rules decide who may read or write it at a time. Those rules are what remove whole classes of bugs.
- Errors are values. There are no exceptions to forget to catch. A function that can fail returns a
Result, and the compiler makes you deal with it. - No
nullorundefined. A value that might be missing is anOption, and you can't use it until you've handled the "missing" case. - One binary out.
cargo build --releaseproduces a single native executable with no Node, nonode_modulesand no interpreter needed where it runs.
How Rust works: the architecture
Node reads your JavaScript at run time and V8 compiles it on the fly, re-optimising hot functions as it learns their types. Rust does all of that work before the program exists, in the rustc compiler. Knowing the stages makes the compiler's error messages much easier to read:
- Parsing and macro expansion. Source text becomes a syntax tree. Macros such as
println!andvec!(the!marks a macro) are expanded into ordinary code here, and#[derive(...)]attributes generate code for your types. - Name resolution and HIR. Names are matched to their definitions and the tree is lowered to the High-level IR, a desugared form (a
forloop becomes aloopwith amatch, for example). - Type checking and trait resolution. Types are inferred and checked, and every method call is matched to a concrete implementation. This is where TypeScript would stop.
- MIR and the borrow checker. The program is lowered again to the Mid-level IR, a control-flow graph of simple statements. The borrow checker walks this graph and proves that every reference is valid and that no value is written while someone else is reading it. Ownership errors come from here.
- Monomorphisation. Generic code (
fn largest<T>) is copied once for each concrete type it is used with, solargest::<i32>andlargest::<f64>become two specialised functions with no runtime type checks. That is why generics in Rust cost nothing at run time. - LLVM and the linker. MIR is translated to LLVM IR, the same optimiser back end used by Clang and Swift, which produces machine code for your target (x86-64, ARM, WebAssembly…). The linker joins your crates into one binary.
Some other pieces of the design are worth knowing up front:
- Zero-cost abstractions. Iterators, closures,
Optionand generics compile down to the same machine code you would have written by hand with loops and pointers. You are not charged at run time for writing at a higher level. - Crates and Cargo. A crate is one compilation unit, a library or a binary (think one npm package). Cargo builds, tests, documents and publishes crates, and crates.io is the registry.
std,allocandcore. The standard library comes in layers.coreworks anywhere, even on a microcontroller with no operating system.allocadds heap types likeVecandString.stdadds files, threads and networking. Embedded and kernel code uses only the lower layers.- Editions. Every few years (2015, 2018, 2021, 2024) an edition bundles small breaking changes to the syntax. Each crate picks its edition in
Cargo.toml, and crates of different editions link together freely, so the ecosystem never splits the way Python 2 and 3 did. - Async without a built-in runtime. The language provides
async/awaitand theFuturetrait, but no event loop. You choose a runtime library; Tokio is the standard one for servers. Rust's futures do nothing until polled, unlike JS promises, which start running as soon as they are created. unsafeas a fenced area. A small set of operations the compiler can't check (raw pointers, calling C) must be written insideunsafe { }. Safe Rust is built on top of small, reviewedunsafecores inside the standard library.
Installing Rust
Install with rustup, the official toolchain manager (the nvm of Rust): on macOS or Linux run the one-liner from rustup.rs, and on Windows download rustup-init.exe from the same site. It installs the compiler rustc, the package manager cargo, the formatter and the linter. Then the commands you'll use every day:
rustup update # update to the newest stable Rustcargo new hello # a new project with Cargo.toml and src/main.rscd hellocargo run # build in debug mode and runcargo check # type-check and borrow-check only, much fastercargo test # run every #[test] functioncargo clippy # the linter, like ESLintcargo fmt # the formatter, like Prettiercargo add serde --features derive # like npm install serdecargo build --release # an optimised binary in target/release/Line by line
- Rust ships a new stable version every six weeks.
rustup updatemoves you to it; old code keeps compiling, because Rust promises stability within an edition. - Creates a folder with
Cargo.toml(yourpackage.json),src/main.rs(the entry point), and a git repository. Usecargo new --lib namefor a library. - Compiles and runs. The first build compiles every dependency and is slow; later ones are incremental. Build output goes into
target/, which plays the role ofdist/and stays out of git. - Runs everything except code generation. Use it while you write; it is the closest thing to
tsc --noEmit. Editors with rust-analyzer run it for you on save. - Finds and runs tests in your code and in
tests/. There is no Jest or Vitest to install: testing is built into the language and Cargo. - Clippy has hundreds of lints that point out non-idiomatic or slow code and usually suggest the fix. Treat it like a reviewer.
- rustfmt formats the code in one official style. Almost every Rust project uses it, so there are no style debates.
- Adds a dependency to
Cargo.toml. A feature switches on optional parts of a crate; serde'sderivefeature gives you#[derive(Serialize, Deserialize)]. - Builds with full optimisations. Debug builds can be ten times or more slower than release builds, so benchmark only release builds.
A project's manifest is a TOML file. Here it is next to what you know:
[package]name = "hello"version = "0.1.0"edition = "2024" [dependencies]serde = { version = "1.0", features = ["derive"] }tokio = { version = "1", features = ["full"] } [dev-dependencies]pretty_assertions = "1"Line by line
- The
[package]table is the top of yourpackage.json: name and version (Cargo uses semver as npm does). - The edition this crate is written in. New projects get
2024. It changes a few syntax rules, never which crates you can use. - Runtime dependencies, like
dependenciesin package.json. Exact versions are pinned inCargo.lock, which plays the role ofpackage-lock.json: commit it for applications. "1.0"means "1.0 or newer, below 2.0", the same as^1.0in npm. Features are chosen per dependency, so you compile only what you use.- Dependencies used only by tests, examples and benchmarks:
devDependencies.
Hello, Rust
Every program starts at fn main. Press Run to compile and run it, then try changing the name.
Tab indents · ⌘/Ctrl + Enter runs · Esc leaves the editor
Line by line
fndeclares a function, andmainis where a binary starts. There is no top-level code as in a JS module: everything runs frommain. Blocks use braces, and indentation is four spaces by convention.letcreates a variable. Its type,&str(a borrowed string), is inferred, as TypeScript infersconst name = "Rust". Statements end with;.- An integer literal. With nothing else to go on, Rust picks
i32, a 32-bit signed integer. There is no singlenumbertype: integers and floats are separate. println!is a macro (the!), not a function: it checks the format string at compile time.{name}inlines a variable, like${name}in a template literal. A typo in the name is a compile error, notundefined.- Empty
{}placeholders are filled by the extra arguments, in order.{}uses the type's user-facingDisplayformat. - Methods are called with a dot, as in JS.
to_uppercasereturns a newString, a growable string on the heap that this variable owns. - Placeholders can be mixed.
len()returns the length in bytes (here the same as letters, because the text is ASCII).
Variables, mutability and shadowing
In TypeScript const stops you reassigning a variable, but the object it points at can still be changed. Rust is stricter and simpler: a binding is immutable all the way down unless you write mut.
const score = 10; // can't be reassignedlet lives = 3; // can belives -= 1;const user = { name: 'Ada' };user.name = 'Grace'; // allowed: const fixes the binding, not the objectLine by line
- In JS you choose between
constandlet; most style guides preferconst. - This is the surprise in JS:
constdoes not make the object immutable. You would needObject.freezeorReadonly<T>for that, and even those are shallow.
Tab indents · ⌘/Ctrl + Enter runs · Esc leaves the editor
Line by line
- A plain
letis immutable, likeconst, but deeply: ifscorewere a struct, none of its fields could change either. - Remove the
//and press Run to see the compiler refuse. Rust's error messages are famously helpful: this one points at both lines and suggests addingmut. let mutmakes the binding mutable, likeletin JS. Mutability is explicit, so a reader can see at a glance which values change.- Shadowing: a second
let inputcreates a new variable with the same name, here with a different type. It is common for "parse then forget the text" steps.parse()returns aResult, andunwrap()takes the value out (or crashes if parsing failed; more on that later). constis a compile-time constant. It needs an explicit type, is written in SCREAMING_CASE, and is inlined wherever it is used. It is closer to a value you would put in a config file than to JSconst.
Types: numbers, text, tuples and arrays
JavaScript has one number (a 64-bit float) and bigint. Rust has a type for each size, so you choose exactly how much memory a value takes and whether it can be negative.
Tab indents · ⌘/Ctrl + Enter runs · Esc leaves the editor
Line by line
- Signed integers are
i8,i16,i32,i64andi128;isizeis the size of a pointer.i32is the default. - Unsigned integers (
u8…u128,usize) can't be negative. Au8holds 0 to 255, one byte.usizeis the type of lengths and indexes. - Floats are
f32andf64;f64is the same as a JS number. A suffix such as_f64sets a literal's type, and_can separate digits anywhere (1_000_000). - A
charis one Unicode scalar value, four bytes, written in single quotes, so a Burmese letter is onechar. Strings use double quotes. 255 + 1doesn't fit in au8. In a debug build plain+would panic (stop the program) on overflow rather than silently wrap. When overflow is possible, say what you want:checked_addreturnsNoneon overflow,wrapping_addwraps around to 0, andsaturating_addstops at the maximum.- Integer division truncates, so
7 / 2is3; with floats it is3.5. In JS both give3.5. - Rust never converts number types for you, not even
i32tof64.asconverts explicitly.a * cwithout it would be a compile error: mixing types is always a decision you write down. - A tuple groups values of different types, like a TS tuple
[number, number]. Destructure it withlet (x, y), or read a field by position with.0,.1. - An array has a fixed length that is part of its type (
[&str; 3]) and lives on the stack. Indexing past the end panics instead of returningundefined. For a list that grows, useVec, shown later.
Functions, and everything is an expression
The biggest change in day-to-day style: in Rust almost everything is an expression that produces a value, including if, match and whole blocks. The last expression of a block, written without a semicolon, is its value.
Tab indents · ⌘/Ctrl + Enter runs · Esc leaves the editor
Line by line
- Parameter types are required, as in strict TypeScript, and come after the name.
-> i32is the return type. Unlike TS, Rust never infers a function's signature: signatures are the contract, and inference works only inside the body. - No
returnand no semicolon: the last expression is returned. Adding a;would turn it into a statement whose value is()(the empty "unit" type, Rust'svoid), and the compiler would complain that the function returns nothing.returnexists for leaving early. &'static stris a string slice that lives for the whole program: string literals are stored in the binary itself, so they are always valid.ifis an expression, so it works as JS's ternary operator does. Every branch must have the same type, and the condition must be a realbool: there is no truthiness, soif 1 { }doesn't compile.- A block in braces is an expression too. The temporary
wandhexist only inside it, and its value isw * h. This is a tidy way to compute a value in a few steps without leaking helper variables.
Control flow: loops and match
Tab indents · ⌘/Ctrl + Enter runs · Esc leaves the editor
Line by line
looprepeats forever until abreak. It replaceswhile (true), and because it is an expression it can produce a value.break countleaves the loop and handscountout as the loop's value, sofoundbecomes 8. In JS you would need a variable declared outside the loop.whileworks as in JS, without parentheses around the condition.print!isprintln!without the newline.forloops over anything iterable.1..=3is an inclusive range (1, 2, 3) and1..3excludes the end (1, 2). There is no C-stylefor (let i = 0; i < n; i++), and you rarely miss it..iter().enumerate()yields(index, item)pairs, destructured in the loop head, likefor (const [i, lang] of langs.entries()).matchisswitchwith superpowers: it is an expression, arms can match ranges, tuples, enums and conditions, and there is no fall-through, so nobreakto forget._matches anything.matchmust be exhaustive: remove this arm and the compiler will tell you which numbers are not covered. That check is what makesmatchon enums so useful later.
Ownership: the idea everything else rests on
Here is the heart of Rust, and the part with no JS equivalent. First, recall how JavaScript treats objects:
const a = { tags: ['ts'] };const b = a; // b and a point at the same objectb.tags.push('rust');console.log(a.tags); // ['ts', 'rust']: changed through bLine by line
- Assigning an object copies the reference. Now two names share one object, and the garbage collector frees it only when neither can reach it any more.
- A change through one name is visible through the other. This sharing is convenient, and it is also the root of many bugs: a function you call might keep a reference and change your data later.
Rust replaces this with three rules that the compiler checks:
- Every value has exactly one owner, a variable.
- When the owner goes out of scope, the value is dropped (its memory is freed) right there. No garbage collector is needed.
- Assigning or passing a heap value moves ownership to the new place. The old name can no longer be used.
Tab indents · ⌘/Ctrl + Enter runs · Esc leaves the editor
Line by line
String::fromputs the text on the heap and makesaits owner.::calls a function that belongs to a type, like a static method.- This is a move, not a shared reference. Ownership passes to
bandabecomes unusable. Only the pointer, length and capacity are copied; the text itself stays where it is. - Uncomment this line and Run: the compiler rejects using
aafter the move. In JS this would silently share the object; in Rust there is never more than one owner to free it. .clone()makes an explicit deep copy, sobandcown separate strings. Clones are visible in the code, so you can see where copying costs you.- Small fixed-size types (integers, floats,
bool,char, and tuples of them) areCopy: assigning them copies the bits and both names stay usable. Moves only matter for values that own something, like heap memory. - Passing a
Stringto a function moves it into the parameters. Whentakereturns,sis dropped and the memory freed. Usingcafter this line would be an error. - Returning a value moves ownership out to the caller, so
givecreates a string anddbecomes its owner. - At the closing brace
banddgo out of scope and their strings are freed, in the reverse order they were created. This is deterministic: you know exactly when memory is released (and when files and sockets are closed, because those are freed the same way).
Borrowing: references without taking ownership
Moving everything into functions would be tiring. Instead you borrow: &value gives a function a reference to read, and &mut value gives it a reference to change. The borrow checker enforces one rule: at any moment you can have either any number of shared & references or exactly one &mut reference, never both.
Tab indents · ⌘/Ctrl + Enter runs · Esc leaves the editor
Line by line
&Stringis a shared reference:lengthcan read the string but not change it, and does not own it, so nothing is freed when it returns. (Idiomatic code takes&strhere; see the next section.)&mut Stringis a mutable reference:shoutmay change the caller's string in place.&wordlends the string out for the duration of the call. Afterwardswordis still ours.- To lend mutably, both the variable (
let mut word) and the borrow (&mut word) must say so. A reader can see at the call site which functions might change their data, which JS never tells you. - Any number of shared borrows can exist together, because readers can't disturb each other.
- A mutable borrow is allowed here because
r1andr2are not used after theprintln!. The compiler tracks where each reference is last used (this is called non-lexical lifetimes), not just the block it is declared in.
Now meet the borrow checker saying no. This program does not compile on purpose. Press Run to read the error, then try the fix in the notes.
Tab indents · ⌘/Ctrl + Enter runs · Esc leaves the editor
Line by line
firstborrows the first element of the vector, a pointer into the vector's heap buffer.pushneeds&mut list. If the buffer is full,pushallocates a bigger one, copies the elements and frees the old buffer.firstwould then point at freed memory: in C this is a classic use-after-free bug.- Because
firstis used after thepush, the shared borrow and the mutable borrow overlap and the compiler refuses (error E0502). To fix it, copy the value out withlet first = list[0];(ani32isCopy), or move theprintln!above thepush.
String and &str
Rust has two main string types, and the split confuses everyone at first. String is an owned, growable buffer, like a StringBuilder. &str (a "string slice") is a borrowed view into text that someone else owns: a literal, part of a String, or a whole one. Both are always valid UTF-8.
Tab indents · ⌘/Ctrl + Enter runs · Esc leaves the editor
Line by line
- Take
&strfor parameters you only read: it accepts literals,Strings and slices alike. ReturnStringwhen you build new text, because the caller needs to own it. format!works likeprintln!but returns theStringinstead of printing it. It is Rust's template literal.- A literal is a
&strpointing into the program binary. - A
Stringlives on the heap and can grow.String::from(…),"…".to_string()and"…".to_owned()all make one. push_strappends in place, which is whyownedismut. Emoji are fine: strings are UTF-8.&ownedborrows theString, and Rust turns&Stringinto&strautomatically (deref coercion). That is why&strparameters accept both.- Slicing takes a byte range:
0..5is "Minga". Slicing in the middle of a multi-byte character panics, so for non-ASCII text, work withchars()or a search instead of fixed numbers. - Burmese characters take three bytes each in UTF-8, so
len()(bytes) andchars().count()(Unicode scalar values) differ: 27 and 9. JavaScript's.lengthcounts UTF-16 code units, a third answer. Count what you actually mean.
Lifetimes, briefly
A lifetime is the compiler's name for "how long a reference stays valid." Most of the time it works them out itself. You write them only when a function returns a reference and the compiler can't tell which input it came from.
Tab indents · ⌘/Ctrl + Enter runs · Esc leaves the editor
Line by line
<'a>declares a lifetime parameter, like a generic. The signature says: the result lives no longer than the shorter-lived ofxandy. It doesn't change how long anything lives; it describes the relationship so the caller can be checked.- A struct that holds a reference must say how long that reference lives: an
Excerptcan't outlive the text it points into. blives only inside this inner block and is freed at its closing brace.longestmay return a reference intob, so keeping that reference past the brace would be an error..to_string()copies the result into a new ownedString, which can live on. Try removing.to_string()to see the error.splityields&strslices borrowed fromtext, with no copying.next()takes the first one as anOptionandunwrap()takes it out.
Structs and methods
Rust has no classes. Data goes in a struct and behaviour in impl blocks. There is no inheritance; sharing behaviour is the job of traits, covered below.
class Account { constructor(public owner: string, private balance = 0) {} deposit(amount: number) { this.balance += amount; } getBalance() { return this.balance; }}Tab indents · ⌘/Ctrl + Enter runs · Esc leaves the editor
Line by line
#[derive]asks the compiler to generate standard trait implementations:Debugfor printing with{:?},Clonefor.clone(), andPartialEqfor==. In JS==on objects compares references; in Rust it compares contents, and only if you ask for it.- A struct is a named set of typed fields, like a TS
interfacethat also decides the memory layout. Fields are private to the module unless markedpub. - Money in the smallest unit (cents, pyas) as an unsigned integer: it can't go negative, and there are no float rounding surprises.
- An
implblock holds the methods of a type. You can have several, even in different files of the same crate. - A function without
selfis an associated function, Rust'sstaticmethod.newis only a convention, not a keyword: there are no constructors.Selfmeans the type being implemented. &mut selfmeans the method borrows the struct mutably, so it can only be called on amutvariable. The signature tells you which methods change the value.&selfborrows it read-only. A field and a method can share a name;acc.balance()calls the method,acc.balancereads the field.- Call associated functions with
::, methods with.. Rust borrows&mut accautomatically foracc.deposit(…). {:?}(here{acc:?}) prints theDebugform, handy while developing, likeconsole.logof an object.{:#?}pretty-prints it over several lines.- Struct update syntax
..acc.clone()fills the remaining fields from another value, like{ ...acc, owner: "Grace" }in JS. We clone because the spread would otherwise moveacc's fields.
Enums and pattern matching
If you use discriminated unions in TypeScript, you already understand Rust enums. Each variant can carry its own data, and match makes sure you handle every one.
type Shape = | { kind: 'circle'; radius: number } | { kind: 'rect'; w: number; h: number }; function area(s: Shape): number { switch (s.kind) { case 'circle': return Math.PI * s.radius ** 2; case 'rect': return s.w * s.h; }}Line by line
- TypeScript narrows on the
kindtag. Whether a missing case is caught depends on your compiler settings and return type; in Rust it always is.
Tab indents · ⌘/Ctrl + Enter runs · Esc leaves the editor
Line by line
- An
enumlists every possible shape of a value. The tag is built in: you never write akindfield yourself. - A variant can carry named fields like a struct, unnamed ones like a tuple (
Circle(f64)), or nothing at all. - A variant with no data is like a member of a TS string-literal union.
matchdestructures each variant and binds its fields as variables. It is exhaustive: add aTrianglevariant and everymatchthat forgets it stops compiling. That turns "I forgot to handle the new case" from a production bug into a compile error.- An array of enums. Every element has the same type,
Shape, even though the variants carry different data. {:.2}prints a float with two decimals, liketoFixed(2).matchworks on any value, not only enums.- A match guard (
if …) adds a condition to an arm.xbinds the matched value so the guard can use it.
Option instead of null and undefined
Tony Hoare called null references his "billion-dollar mistake." Rust has no null. A value that might be absent has the type Option<T>, an enum with two variants, Some(value) and None, and you must deal with None before you can use the value.
Tab indents · ⌘/Ctrl + Enter runs · Esc leaves the editor
Line by line
- The return type says honestly that there might be no user, as
string | undefinedwould in TS. The difference: anOption<&str>is not a&str, so you can't call string methods on it by accident. - Matching on both variants is the most explicit way to handle it.
if lethandles only the case you care about, likeif (user !== undefined)with narrowing.unwrap_orsupplies a default, like?? "guest"in JS.maptransforms the value inside aSomeand leavesNonealone, like optional chaininguser?.length.|n| n.len()is a closure, Rust's arrow function(n) => n.length.- Prints
Some(3): theDebugform shows the wrapper. let … elseunwraps or leaves: if the pattern doesn't match, theelseblock must exit (return,breakor panic). It is the Rust version of an early-return guard clause.
Error handling with Result and ?
In JS any function might throw, and nothing in its signature says so. In Rust a function that can fail returns Result<T, E>, an enum of Ok(value) or Err(error). The ? operator propagates errors upward with one character, which gives you the convenience of exceptions with the errors listed in the type.
function parsePort(s: string): number { const n = Number(s); if (!Number.isInteger(n) || n < 1 || n > 65535) throw new Error('bad port: ' + s); return n;}Line by line
- Nothing in
parsePort's type tells the caller it can throw, so forgettingtry/catchis easy and only shows up at run time.
Tab indents · ⌘/Ctrl + Enter runs · Esc leaves the editor
Line by line
usebrings names into scope, likeimport.std::fmtis a module path;::separates its parts.- Your own error type is an enum listing every way this code can fail. One variant wraps the standard library's parse error, the other carries the bad number.
- Implementing the
Displaytrait decides how the error prints with{}.write!isformat!aimed at a formatter. (In real projects the thiserror crate generates this impl from one attribute per variant.) - A
Fromimpl tells?how to convert aParseIntErrorinto ourConfigErrorautomatically. - The signature says "a
u16or aConfigError." Callers must handle both; ignoring aResultgives a compiler warning. - The
?operator: ifparse()returnedErr, convert the error withFromand return it from this function right away; ifOk, take the value out. It replaces a wholetry/catch/rethrow. The target typeu32tellsparsewhat to parse into. - Returning an error early is ordinary control flow:
Err(…)is just a value. - Success is wrapped in
Ok. Theas u16is safe because the range was checked just above. - Matching on the
Resulthandles both outcomes. Notice how the three inputs take different paths. There is alsounwrap()andexpect("message"), which crash onErr: fine in examples and tests, a code smell in production paths.
Traits and generics
A trait is a set of methods a type promises to have, like a TS interface. The difference: in TS any object with the right shape fits an interface (structural typing), but in Rust a type implements a trait only if someone writes impl Trait for Type (nominal typing). You can implement your traits for other people's types, and other people's traits for yours.
Tab indents · ⌘/Ctrl + Enter runs · Esc leaves the editor
Line by line
- A trait declares method signatures that implementing types must provide.
- A trait method can have a default body, like a mixin. Types get it for free and may override it.
impl Describe for Dogis the explicit opt-in.Dogis a unit struct with no fields, which is useful when a type only carries behaviour..into()converts the&strinto theStringthe signature wants.Robotoverrides the defaultdescribe.&impl Describemeans "a reference to any type that implementsDescribe." It is static dispatch: the compiler generates a separate copy ofannounceforDogand forRobot, so the calls are direct and can be inlined.- A generic function with trait bounds:
Tmust be comparable with>(PartialOrd) and copyable (Copy). Like<T extends …>in TS, but bounds list capabilities, and the compiler checks the body uses only those.&[T]is a slice, a borrowed view of any array orVec. dyn Describeis dynamic dispatch: a trait object whose method is looked up in a table at run time, like a JS method call.Boxputs each value on the heap so that values of different sizes can sit in one list.- Several type parameters, each bound to
Display(can be printed with{}). Strings and integers both qualify. - A
Vecof trait objects holds different concrete types in one collection, the closest thing to a TS array ofDescribe. The type annotation tells the compiler to convert eachBox<Dog>andBox<Robot>intoBox<dyn Describe>.
Collections, iterators and closures
Your favourite array methods are all here, with two differences: you call .iter() to start a chain, and iterators are lazy. Nothing runs until something consumes them (sum, collect, a for loop). The compiler then fuses the whole chain into one loop, with no temporary arrays in between.
const prices = [12, 5, 30, 8];const total = prices.filter((p) => p > 6).map((p) => p * 2).reduce((a, b) => a + b, 0);Line by line
- In JS each step builds a new array:
filtermakes one,mapanother, thenreducewalks it.
Tab indents · ⌘/Ctrl + Enter runs · Esc leaves the editor
Line by line
Vec<T>is the growable list, Rust'sArray.vec![…]is a macro that builds one from a literal. It must bemuttopush.- The same chain as the TS version.
.iter()borrows each element without consuming the vector, and.sum()drives the chain. The type annotation ontotaltellssumwhat to produce. - The closure receives
&&i32(a reference to the iterator's reference), and the pattern|&&p|strips both. You'll often see|p| **p > 6or.copied()instead (as in note 5). This is the noisiest part of iterator code, and the compiler tells you when you get it wrong. collect()gathers an iterator into a collection. It can build many kinds (Vec,HashMap,String…), so you say which with a type annotation.joinworks as in JS..copied()turns&i32items into plaini32values.Vec<_>lets the compiler fill in the element type. The closure usesthresholdfrom the surrounding scope, exactly like a JS closure.HashMapisMap. Keys and values are typed. It is not ordered: iteration order is unspecified, so don't rely on it.- The entry API: get the value for a key, inserting
0if it is missing, and add to it in place.*dereferences the&mut u32thator_insertreturns. One lookup instead of JS'smap.set(k, (map.get(k) ?? 0) + 5). - For stable output, collect the keys and sort them.
sortsorts in place; for the reverse order, there issort_by(|a, b| b.cmp(a)). getreturns anOption, because the key may be missing.stock["plums"]would panic instead.- Nothing has run yet on this line:
lengthsis just a description of work.max()consumes it and returnsOption<usize>, because an empty iterator has no maximum.
Modules, crates and visibility
An ES module is a file. A Rust module is a namespace you declare with mod, either inline or in its own file. Everything is private by default, and pub exports it.
Tab indents · ⌘/Ctrl + Enter runs · Esc leaves the editor
Line by line
mod geometry { … }declares a module inline. In a real project you writemod geometry;and Cargo readssrc/geometry.rs(orsrc/geometry/mod.rs).pubon the struct makes the type visible outside the module, andpubon each field makes that field readable and writable. Withoutpub x, outside code could hold aPointbut not build one withPoint { x, y }, which is how you keep invariants: hide the fields and offer anewthat checks them.- No
pub:squareis private togeometryand its children, like a function a JS module doesn't export. - Modules nest.
geometry::shapesis a child module. superis the parent module, like../in an import path.crate::starts from the root of the crate, like an absolute path.usebrings a path into scope, likeimport { Point } from "./geometry". Other crates work the same way: aftercargo add rand, you writeuse rand::Rng;.
On disk, the same crate split across files looks like this. There is no index.ts: the mod lines in the parent file are what include the children.
Cargo.tomlsrc/├── main.rs mod geometry; ← includes src/geometry.rs├── lib.rs (optional: the library half of the crate)├── geometry.rs pub mod shapes; ← includes src/geometry/shapes.rs└── geometry/ └── shapes.rstests/└── api.rs integration tests, compiled as a separate crateLine by line
main.rsis the root of a binary crate,lib.rsof a library crate. A file that nomodline mentions is simply not compiled: no unused file can sneak into the build.- A package can hold a library and a binary together. The binary then uses the library through
use your_crate_name::…, like any other dependency. - Files in
tests/can use only your crate's public API, like a consumer would. Unit tests live next to the code instead, as shown next.
Testing is built in
No Jest, no Vitest, no config: mark a function #[test] and cargo test runs it. Press Run on this block: a file with tests and no main runs as cargo test here too.
Tab indents · ⌘/Ctrl + Enter runs · Esc leaves the editor
Line by line
- The function under test,
pubso other modules (and the test module) can use it. splitaccepts a closure that decides which characters are separators.c: charneeds its type written here becausesplitaccepts several kinds of pattern.- The "turbofish"
::<Vec<_>>givescollectits target type inline, instead of annotating a variable. #[cfg(test)]compiles this module only forcargo test, so tests and test helpers never end up in your release binary.- The test module is a child, so
super::*imports everything from the parent, private functions included. In Rust you can unit-test private code directly. #[test]marks a test. Tests run in parallel threads by default, so they must not depend on each other.assert_eq!compares and, on failure, prints both sides. There are alsoassert!(condition)andassert_ne!. Try breaking the expected string and Run again to see a failure report.#[should_panic]passes only if the body panics, which is how you test that invalid input is rejected.
Smart pointers: Box, Rc and RefCell
Sometimes one owner is not enough, or a value has to live on the heap. The standard library has a few smart pointers for those cases. Each one is an ordinary struct that owns some heap memory and frees it when dropped.
Tab indents · ⌘/Ctrl + Enter runs · Esc leaves the editor
Line by line
- A recursive type needs indirection: a
Listcan't contain a wholeListdirectly, or its size would be infinite.Box<List>is a pointer to a heap-allocatedList, with a known size.Boxis the simplest smart pointer: a single owner of something on the heap. - A
Boxdereferences automatically, sorest(a&Box<List>) can be passed where a&Listis expected. - Building the list
1 → 2 → end. Whenlistis dropped, eachBoxfrees its node in turn. Rc("reference counted") allows several owners of one value, in a single thread. It is the closest thing to how JS objects work: the value is freed when the last owner goes away.Rc::clonedoesn't copy the string; it adds one to the count and returns another pointer to the same value. TheRc::clone(&x)spelling (rather thanx.clone()) makes that visible to readers.- Three owners now. Values inside an
Rcare read-only, because shared plus mutable is exactly what the borrow rules forbid. RefCellmoves the borrow check from compile time to run time: you may change the contents through a shared handle, and it panics if two mutable borrows ever overlap.Rc<RefCell<T>>is the usual recipe for shared, mutable state in one thread.borrow_mut()hands out a temporary mutable borrow, which ends at the end of the statement.borrow()is a temporary shared borrow. Use these types sparingly: most Rust code needs none of them, and reaching forRc<RefCell<…>>everywhere is often a sign of a design that fights ownership.
Fearless concurrency: threads, channels and locks
JavaScript has one thread per event loop and offers Web Workers that share nothing by default. Rust gives you real OS threads that can share memory, and the same ownership rules stop data races at compile time. Two marker traits do the work: Send (safe to move to another thread) and Sync (safe to share between threads). The compiler simply refuses to send an Rc across threads, for example.
Tab indents · ⌘/Ctrl + Enter runs · Esc leaves the editor
Line by line
- Start three threads, one per id, and keep their handles. The
collect()matters: iterators are lazy, so without it no thread would start until later. thread::spawnruns a closure on a new OS thread.movemakes the closure take ownership of what it uses (id), because the thread may outlive the current function, so borrowing would be unsafe.join()waits for a thread and returns its result, like awaiting a promise. It is aResult, because the thread may have panicked.- To share a counter:
Mutexguards the value so only one thread at a time can touch it, andArc("atomic reference counted") is the thread-safeRcthat lets every thread own a handle to it. - Each thread gets its own
Arcclone to move in. The data itself is not copied. lock()waits for the mutex and returns a guard that dereferences to the value. The lock is released when the guard is dropped, here at the end of the statement, so you can't forget to unlock. Try replacingArc<Mutex<i32>>with a plainlet mut counter = 0captured by every thread: the compiler rejects it instead of letting a race lose updates.- Always 8000. With unsynchronised threads in C this could print any number; in Rust that version doesn't compile.
- A channel, like Go's:
txsends andrxreceives.mpscmeans multiple producers, single consumer, so each thread gets a clone oftx. - The receiving loop ends when every sender is gone. The worker threads drop their clones when they finish; this drops the original.
rx.iter()blocks for each message until the channel closes. The threads finish in any order, so the messages are sorted to print stably.- Scoped threads may borrow local data (
data) withoutmoveorArc, becausethread::scopewaits for all of them before it returns, so they can't outlive what they borrow.
Async Rust with Tokio
Threads are good for CPU work. For thousands of network connections you want async, which you know well from JS. The syntax is nearly the same; the model has two differences. A Rust Future is lazy: it does nothing until it is awaited or spawned, whereas a JS promise starts the moment you create it. And there is no built-in event loop: you add a runtime crate, almost always Tokio.
const sleep = (ms: number) => new Promise((r) => setTimeout(r, ms));async function fetchUser(id: number) { await sleep(100 * id); return 'user ' + id;}const [a, b] = await Promise.all([fetchUser(1), fetchUser(2)]);Line by line
Promise.allruns both at once and waits for both: about 200 ms in total, not 300.
Tab indents · ⌘/Ctrl + Enter runs · Esc leaves the editor
Line by line
- An
async fnreturns aFuture<Output = String>instead of aString, as anasync functionreturns aPromise<string>. .awaitis a postfix keyword (written after the expression), so chains read left to right:client.get(url).send().await?.json().await?. Use Tokio'ssleep, neverstd::thread::sleep, which would block the whole worker thread.- This attribute macro starts the Tokio runtime and runs
mainon it. Tokio provides the scheduler, timers, TCP and file I/O, and channels. - Calling an async fn only builds the future. Nothing happens here: no timer starts, and dropping it simply discards it. A JS promise would already be running.
join!polls both futures concurrently in the current task and waits for both:Promise.all. (There is alsoselect!, which takes whichever finishes first:Promise.race.)tokio::spawnhands a future to the runtime as an independent task, which may run on another worker thread, like firing off a promise without awaiting it yet. Tasks are cheap: a server can have hundreds of thousands.- Awaiting a
JoinHandlegives aResult:Errif the task panicked or was cancelled. timeoutwraps any future with a deadline and returnsErr(Elapsed)if it isn't done in time. The 500 ms fetch is dropped (cancelled) at 50 ms. In Rust, dropping a future is how you cancel it.- The whole run takes about 550 ms. Run sequentially, the same awaits would take over 900 ms.
JSON with serde
In TypeScript you might JSON.parse and then check the shape with Zod. In Rust, serde derives the parser from your struct, so the shape is checked while parsing and you get a typed value or a precise error. It is one of the most used crates in the ecosystem.
Tab indents · ⌘/Ctrl + Enter runs · Esc leaves the editor
Line by line
- Deriving
SerializeandDeserializegenerates the JSON code at compile time. No reflection happens at run time, which is why serde is very fast. - Rust fields are snake_case and JSON is often camelCase; this attribute maps
display_nameto"displayName". - An
Optionfield acceptsnullor a missing key, asemail?: string | nullwould in TS. #[serde(default)]fills a missing key with the type's default, an emptyVec.mainitself can return aResult, so?works in it. AnErrends the program with the error printed and a failing exit code.- A raw string
r#"…"#needs no escaping of the inner quotes, which is handy for JSON and regexes. - Parse straight into a
User. The type annotation picks what to parse into: this is the Zod schema and the TS type in one. - Wrong types are caught while parsing, with the position in the input.
unwrap_err()takes the error out (and would panic if parsing had succeeded). - Serialising goes the other way. The struct update syntax makes a modified copy of
userwith one tag. serde_json::Valueis for JSON whose shape you don't know, likeunknownin TS. Indexing a missing key givesnullinstead of panicking.
unsafe, briefly
Everything so far was safe Rust: the compiler proves there are no dangling pointers or data races. Inside an unsafe block you can do five extra things, the main ones being dereferencing raw pointers and calling C functions. The compiler still checks everything else; unsafe only means "I, the programmer, guarantee these few operations are valid."
Tab indents · ⌘/Ctrl + Enter runs · Esc leaves the editor
Line by line
- A raw pointer
*mut i32is a C-style pointer. Making one is safe; it is reading or writing through it that the compiler can't check. - Dereferencing it must be inside
unsafe. The block marks exactly where a reviewer needs to look. get_uncheckedskips the bounds check thatv[2]does. With an index past the end, this is undefined behaviour, which is why it isunsafe. Measure before reaching for tricks like this: the optimiser usually removes bounds checks it can prove are unneeded. Application code almost never needsunsafe.
A small project: a word-frequency report
Here is one program that uses most of what came before: a constant, a struct, a trait implementation, an iterator pipeline, a HashMap, sorting with a custom order, and Result. Change the text or the limit and run it.
Tab indents · ⌘/Ctrl + Enter runs · Esc leaves the editor
Line by line
- A
conststring slice. In a real CLI this would come from a file (std::fs::read_to_string(path)?) or from standard input. - The result type: a total and the top words with their counts, as
(String, usize)tuples. - Implementing
Displaylets the report print itself with{}, like overridingtoString().writeln!writes a line into the formatter, and?passes on any write error. - Format specs:
{:>2}right-aligns in two columns and{word:<6}left-aligns in six, so the bars line up."#".repeat(n)builds the bar, like"#".repeat(n)in JS.*ndereferences the&usizethe iterator gives. - Returns an owned map of word to count. The map's types are inferred from how it is used below.
- Splitting on any non-letter handles spaces, full stops and commas in one go.
str::to_lowercasepasses a method as a function, as.map(s => s.toLowerCase())would, but shorter.- An error for empty input. A plain
Stringis fine as an error type in a small program. into_iter()consumes the map, moving its keys and values into the vector, so nothing is copied. After this,countscan't be used.- Sort by count, highest first (
bbeforea), and break ties alphabetically withthen_with.cmpreturns anOrdering(Less,EqualorGreater), Rust's version of a comparator's negative, zero or positive number. - Handle both outcomes.
eprintln!writes to standard error, likeconsole.error.
Rust in the browser: WebAssembly
This is where Rust meets your existing stack. Rust compiles to WebAssembly, and wasm-bindgen generates the JS glue and TypeScript types, so a TS app can call Rust functions as if they were an npm module. It suits heavy work in the browser: image and video processing, parsers, compression, games, cryptography.
use wasm_bindgen::prelude::*; #[wasm_bindgen]pub fn slugify(title: &str) -> String { title.to_lowercase().split_whitespace().collect::<Vec<_>>().join("-")}Line by line
- After
cargo add wasm-bindgen, the prelude brings in the attribute and the JS interop types. InCargo.tomlthe crate also needscrate-type = ["cdylib"]under[lib]. #[wasm_bindgen]exports the function to JavaScript and generates the code that copies strings across the boundary between JS and Wasm memory.
rustup target add wasm32-unknown-unknowncargo build --release --target wasm32-unknown-unknownwasm-bindgen --target web --out-dir pkg target/wasm32-unknown-unknown/release/slug.wasmLine by line
- Installs the WebAssembly target once, then builds the crate as a
.wasmfile. - The
wasm-bindgenCLI (cargo install wasm-bindgen-cli) writespkg/slug.js,pkg/slug.d.tsand the.wasmfile. Bundlers such as Vite can import them directly.
import init, { slugify } from './pkg/slug.js'; await init();console.log(slugify('Zero to Hero in Rust')); // "zero-to-hero-in-rust"Line by line
- An ordinary ES module import. The generated
.d.tsfile givesslugifythe type(title: string) => string, so your editor autocompletes it. init()downloads and instantiates the.wasmfile once.- From here on it is a normal function call. The Rust code runs at near-native speed inside the browser's sandbox.
What you can build with Rust
- Command-line tools.
clapparses arguments and the result is one fast binary. ripgrep, bat, fd, eza, starship and uv (the Python package manager) are all Rust. - Web backends and APIs. Axum (from the Tokio team), Actix Web and Rocket, with SQLx or Diesel for databases. Services use little memory and have very steady latency.
- JavaScript tooling. Much of the modern JS toolchain is now Rust: SWC (the compiler behind Next.js), Turbopack (the default bundler in Next.js 16), Rspack, Rolldown and Oxc (the Vite team's bundler and parser), Biome (formatter and linter), Lightning CSS, and the Deno runtime.
- WebAssembly for the browser, edge platforms (Cloudflare Workers runs Wasm next to JS) and plugin systems.
- Desktop and mobile apps. Tauri gives you a web front end (React, Svelte, …) with a Rust back end, and the apps are much smaller than Electron ones.
- Systems software: operating-system components (Linux kernel drivers, parts of Windows, Android), databases, proxies (Cloudflare's Pingora), virtual machines (AWS Firecracker).
- Embedded and IoT.
no_stdRust runs on microcontrollers with no operating system, through theembassyasync framework and the HAL crates for each chip. - Games, graphics and data. The Bevy engine,
wgpufor GPU work on every platform, and Polars, a DataFrame library much faster than pandas.
Pros and cons
Where Rust shines
- Memory safety without a garbage collector. Google reported in November 2025 that memory-safety bugs fell below 20% of all Android vulnerabilities for the first time, and that its Rust code has about a thousand times fewer such bugs than its C and C++.
- Speed and predictability. Performance matches C and C++, and with no GC there are no pause-time spikes, which is exactly why Discord and Figma moved services to it.
- "If it compiles, it usually works." The type system,
Option,Resultand exhaustivematchcatch whole categories of bugs before anything runs, so refactoring a large codebase is far less frightening. - Fearless concurrency. Data races are compile errors.
- Excellent tooling. Cargo, rustfmt, Clippy, rust-analyzer and built-in tests and docs (
cargo doc) come with every install and work the same way in every project. - Compiler messages that teach. The errors explain what went wrong and usually suggest the fix.
- Runs almost everywhere: servers, browsers via Wasm, phones, microcontrollers, as one dependency-free binary.
Where it hurts
- A steep learning curve. Ownership, borrowing and lifetimes take weeks to feel natural. Expect a period of "fighting the borrow checker." It ends, and the habits it leaves make you a better programmer in any language.
- Slow compile times. Monomorphisation and LLVM optimisation cost time. A large project can take minutes for a clean build.
cargo check, incremental builds and splitting into crates help. - Slower to prototype. For a quick script or an experiment, TypeScript or Python get you there faster. Rust asks you to handle every error and edge case up front.
- Async has rough edges. Pinning,
Sendbounds on futures and choosing a runtime are harder than JS's async, and error messages there are at their least friendly. - A younger ecosystem in some areas. Web back ends and CLIs are mature; GUI toolkits, some enterprise SDKs and ML training are thinner than in JS, Python or Java.
- Hiring and team ramp-up. Fewer developers know Rust well than TS or Go, so a team adopting it needs time to train.
Rust, Go and TypeScript side by side
These three often live in the same company, so it helps to know what each is best at:
- Memory. TS (V8) and Go are garbage-collected; Rust uses ownership and has no GC. Go's GC pauses are short, but under heavy load they still show up in tail latency.
- Concurrency. TS: one event loop with async/await. Go: goroutines and channels built into the language, the simplest model of the three. Rust: threads plus async, with data races ruled out at compile time.
- Type system. TS: structural and powerful, but erased and unsound by design (
any). Go: simple, nominal, with generics since 1.18 and deliberately few features. Rust: rich (enums with data, traits, generics, lifetimes) and sound. - Errors. TS: exceptions. Go:
if err != nilreturn values. Rust:Resultwith?, explicit like Go but much shorter. - Learning curve. Go is the easiest to pick up (a weekend), TS is second if you know JS, and Rust is the hardest (weeks to months).
- Best at. TS: UIs, full-stack web apps and sharing code between browser and server. Go: network services, CLIs and cloud infrastructure, quickly and with small teams (Docker, Kubernetes and Terraform are written in Go). Rust: performance-critical paths, systems software, tooling, embedded and Wasm.
How major companies use TypeScript/JavaScript, Go and Rust together
Big companies rarely pick a single language. A common pattern is TypeScript for the product surface, Go for the bulk of network services and infrastructure, and Rust where latency, memory or safety matter most. Some well-documented examples:
- Discord. The desktop and web clients are JavaScript (React, with Electron on desktop). The Read States service, which tracks what every user has read, was written in Go and moved to Rust because Go's garbage collector caused latency spikes every couple of minutes. Even the first, unoptimised Rust version outperformed the tuned Go one.
- Figma. The editor UI is TypeScript and its rendering engine is C++ compiled to WebAssembly. The multiplayer server that syncs documents started in TypeScript and was rewritten in Rust when it could no longer keep up, partly because of garbage-collection pauses. The widely used esbuild bundler, written in Go, was created by Figma co-founder Evan Wallace.
- Microsoft. TypeScript 7 (2026) replaced the JavaScript-based compiler with a native one written in Go, about ten times faster on large projects. The team chose Go because its structure and garbage collection let them port the existing code nearly line by line. Microsoft also ships Rust in the Windows kernel, and Azure's CTO has urged new non-GC projects to use Rust instead of C/C++.
- Cloudflare. Developers write Workers in JavaScript and TypeScript (or Rust compiled to Wasm). Its HTTP proxy, Pingora, was written in Rust to replace NGINX and handles over a trillion requests a day; it is now open source. Many internal services are written in Go.
- Vercel. Next.js is TypeScript, but the compiler underneath is Rust: SWC transforms the code, and Turbopack, also Rust, became the default bundler in Next.js 16.
- Amazon Web Services. Firecracker, the micro-VM that runs every AWS Lambda function and Fargate task, is written in Rust. AWS CDK, its infrastructure-as-code toolkit, is written in TypeScript, and AWS maintains official SDKs for Go, JavaScript and Rust.
- Google. It created Go and uses it across its cloud, and Angular (TypeScript) is its front-end framework. It writes new low-level Android code in Rust, with the security results above.
- Dropbox. Its desktop sync engine, Nucleus, was rewritten in Rust, using the type system to "design away invalid states" so the engine is easier to test.
- The Linux kernel. At the December 2025 Maintainers Summit, Rust was declared no longer experimental: it is now a permanent part of the kernel alongside C.
The lesson for a JS/TS developer: you don't have to choose. Keep TypeScript where it shines, and reach for Go when you need a simple, fast service, or for Rust when you hit a real performance, memory or safety wall. Since so much of your toolchain is now written in Go and Rust, learning them also lets you read and contribute to the tools you use every day.
Your roadmap from zero to hero
- Week 1: the basics. Install with rustup, set up rust-analyzer in your editor, and run every example on this page, changing each one until it breaks. Read chapters 1–6 of The Rust Programming Language ("the book", free at doc.rust-lang.org/book).
- Week 2: ownership for real. Do the Rustlings exercises. When the borrow checker complains, read the whole error message, and the suggested fix, before changing anything.
- Week 3: a CLI tool. Rewrite a small Node script you use in Rust with
clap,serdeandanyhow. Make Clippy pass with no warnings. - Week 4: traits, generics and testing. Finish the book up to chapter 11 and write unit tests for your CLI.
- Month 2: async and the web. Work through the Tokio tutorial, then build a small JSON API with Axum and SQLx, and call it from a TypeScript front end.
- Month 3: go deeper. Concurrency (book chapter 16), smart pointers and lifetimes in depth, and Rust for Rustaceans by Jon Gjengset. Compile something to WebAssembly and use it from a React app.
- Keep going. Read the release notes every six weeks, follow This Week in Rust, and read the source of tools you already use, such as SWC, Biome or ripgrep. Fix a small issue in one of them.
Rust makes you think about ownership up front, the thing JavaScript lets you ignore until it bites. The compiler is strict, but it is on your side: once it lets you through, a whole category of late-night bugs is gone.
JavaScript ဒါမှမဟုတ် TypeScript နဲ့ အသက်မွေးနေသူဆိုရင် Rust က တခြားဂြိုဟ်က ဘာသာစကားလို ထင်ရနိုင်ပါတယ် — lifetime တွေ၊ borrow checker တွေ၊ &mut တွေ၊ စာကြောင်းနှစ်ဆယ်လောက် ရှည်တဲ့ compiler error တွေ။ ဒါပေမဲ့ အထဲကို ဝင်ကြည့်ရင် အများကြီးက ရင်းနှီးနေပါလိမ့်မယ်။ Closure တွေ၊ map နဲ့ filter ပါတဲ့ iterator တွေ၊ async/await၊ npm လိုပဲ အလုပ်လုပ်တဲ့ package manager၊ ပြီးတော့ TypeScript လုပ်ပေးတာတွေကို လုပ်ပေးရုံမက program က free လုပ်ပြီးသား memory ကို မထိနိုင်ကြောင်း၊ shared data ပေါ်မှာ race မဖြစ်နိုင်ကြောင်းပါ သက်သေပြပေးတဲ့ type system တစ်ခု ပါပါတယ်။
ဒီ guide က သုညကနေ စပြီး တကယ့် Rust code ကို ဖတ်နိုင်၊ program အသေးလေးတစ်ခု ကိုယ်တိုင် ရေးနိုင်တဲ့ အဆင့်အထိ ခေါ်သွားပါမယ်။ အပိုင်းတိုင်းမှာ Rust idea တစ်ခုကို သင်သိပြီးသား JS/TS idea နဲ့ ဘေးချင်းယှဉ်ပြထားပါတယ်။ ဥပမာတိုင်းအောက်မှာ တစ်ကြောင်းချင်း မှတ်စု တွေ ပါပါတယ် — highlight လုပ်ထားတဲ့ စာကြောင်းကို နှိပ်ပြီး (ဒါမှမဟုတ် hover လုပ်ပြီး) ဘာလုပ်လဲ ဖတ်ကြည့်ပါ။ Rust ဥပမာတွေက live ပါ။ ပြင်ပြီး Run ကို နှိပ်ရင် (ဒါမှမဟုတ် ⌘/Ctrl + Enter) တရားဝင် Rust Playground ပေါ်မှာ compile လုပ်ပြီး run ပေးပါတယ်၊ ဒီစာမျက်နှာပေါ်မှာတင်ပါပဲ။
JS/TS developer တစ်ယောက်အတွက် ဘာကြောင့် Rust လဲ
Rust ကို ၂၀၀၆ ခုနှစ်မှာ Mozilla က Graydon Hoare ရဲ့ ကိုယ်ပိုင် project လေးအဖြစ် စခဲ့ပြီး ၂၀၀၉ ကစပြီး Mozilla က ထောက်ပံ့ခဲ့ကာ ၂၀၁၅ မေလမှာ 1.0 ထွက်ခဲ့ပါတယ်။ ၂၀၂၁ ကစပြီး AWS၊ Google၊ Microsoft နဲ့ Huawei တို့ အဖွဲ့ဝင်ဖြစ်တဲ့ လွတ်လပ်တဲ့ Rust Foundation က စောင့်ရှောက်နေပါတယ်။ Stack Overflow survey မှာ developer တွေက ၂၀၁၆ ကစပြီး နှစ်တိုင်း အချစ်ဆုံး (most admired) ဘာသာစကားအဖြစ် ရွေးခဲ့ကြပါတယ်။
အရင်က တစ်ခုရရင် တစ်ခုစွန့်ရတဲ့ အရာနှစ်ခုကို တစ်ပြိုင်နက် ပေးဖို့ ရည်ရွယ်ပါတယ် — C နဲ့ C++ ရဲ့ အမြန်နှုန်းနဲ့ ထိန်းချုပ်နိုင်မှု၊ ပြီးတော့ JavaScript လို garbage-collected ဘာသာစကားရဲ့ memory safety၊ garbage collector မပါဘဲနဲ့ပေါ့။ သင်သိပြီးသားတွေနဲ့ ယှဉ်ရင် ဘာတွေ ပြောင်းသွားလဲ ကြည့်ရအောင်—
- Garbage collector မရှိ၊ runtime မရှိ။ V8 က မသုံးတော့တဲ့ object တွေကို ရှာပြီး free လုပ်ဖို့ ခဏခဏ ရပ်ပါတယ်။ Rust ကတော့ value တစ်ခုချင်းကို compiler က ကြိုဆုံးဖြတ်ထားတဲ့ နေရာမှာ free လုပ်လို့ ရပ်တာ မရှိဘဲ memory သုံးစွဲမှုက တည်ငြိမ်ပြီး ကြိုခန့်မှန်းလို့ ရပါတယ်။
- Type တွေကို စစ်ပြီးမှ compile လုပ်ရင်း ဖယ်ပစ်တယ်။ TypeScript လိုပဲ Rust က program မ run ခင် type စစ်ပါတယ်။ TypeScript နဲ့ မတူတာက
anyမရှိ၊as unknown asမရှိပါဘူး၊ ပြီးတော့ ထွက်လာမယ့် machine code ကို type တွေက ဆုံးဖြတ်ပါတယ်။ - "အရာအားလုံးက shared reference" ဆိုတာကို ownership က အစားထိုးတယ်။ JS မှာ variable ဘယ်နှစ်ခုမဆို object တစ်ခုတည်းကို ညွှန်ပြီး ပြင်နိုင်ပါတယ်။ Rust မှာတော့ value တစ်ခုစီမှာ owner တစ်ယောက်ပဲ ရှိပြီး ဘယ်သူက ဘယ်အချိန် ဖတ်ခွင့်/ရေးခွင့် ရှိလဲဆိုတာကို borrowing rule တွေက ဆုံးဖြတ်ပါတယ်။ Bug အမျိုးအစား တစ်ခုလုံးကို ဖယ်ရှားပေးတာ အဲဒီ rule တွေပါပဲ။
- Error တွေက value တွေပါ။ Catch ဖို့ မေ့သွားနိုင်တဲ့ exception မရှိပါဘူး။ Fail ဖြစ်နိုင်တဲ့ function က
Resultကို return ပြန်ပြီး compiler က သင့်ကို ကိုင်တွယ်ခိုင်းပါတယ်။ nullလည်းမရှိ၊undefinedလည်းမရှိ။ မရှိနိုင်တဲ့ value ကOptionဖြစ်ပြီး "မရှိ" ဆိုတဲ့ case ကို ကိုင်တွယ်ပြီးမှ သုံးလို့ ရပါတယ်။- Binary တစ်ခုတည်း ထွက်လာတယ်။
cargo build --releaseက native executable တစ်ခုတည်း ထုတ်ပေးလို့ run မယ့်နေရာမှာ Node လည်း မလို၊node_modulesလည်း မလို၊ interpreter လည်း မလိုပါဘူး။
Rust ဘယ်လို အလုပ်လုပ်လဲ — architecture
Node က သင့် JavaScript ကို run နေချိန်မှာ ဖတ်ပြီး V8 က ချက်ချင်း compile လုပ်ကာ မကြာခဏ ခေါ်တဲ့ function တွေကို type တွေ သိလာသလို ပြန်ပြီး optimise လုပ်ပါတယ်။ Rust ကတော့ အဲဒီအလုပ်အားလုံးကို program မဖြစ်လာခင်မှာပဲ rustc compiler ထဲမှာ လုပ်ပါတယ်။ အဆင့်တွေကို သိထားရင် compiler ရဲ့ error message တွေကို ဖတ်ရတာ အများကြီး ပိုလွယ်ပါတယ်—
- Parsing နဲ့ macro expansion။ Source text က syntax tree ဖြစ်လာပါတယ်။
println!၊vec!လို macro တွေ (!က macro ဖြစ်ကြောင်း ပြတာပါ) ကို ဒီမှာ ရိုးရိုး code အဖြစ် ချဲ့ပြီး#[derive(...)]attribute တွေက သင့် type တွေအတွက် code ထုတ်ပေးပါတယ်။ - Name resolution နဲ့ HIR။ နာမည်တွေကို သူတို့ရဲ့ definition တွေနဲ့ ချိတ်ပြီး tree ကို High-level IR ဆိုတဲ့ ရိုးရှင်းအောင် ပြောင်းထားတဲ့ ပုံစံ (desugared form) ဆီ ချပါတယ် (ဥပမာ
forloop ကmatchပါတဲ့loopဖြစ်သွားပါတယ်)။ - Type checking နဲ့ trait resolution။ Type တွေကို infer လုပ်ပြီး စစ်ကာ method call တိုင်းကို တိကျတဲ့ implementation တစ်ခုနဲ့ ချိတ်ပါတယ်။ TypeScript ဆိုရင် ဒီမှာ ရပ်သွားမှာပါ။
- MIR နဲ့ borrow checker။ Program ကို Mid-level IR ဆိုတဲ့ ရိုးရှင်းတဲ့ statement တွေနဲ့ ဖွဲ့ထားတဲ့ control-flow graph ဆီ ထပ်ချပါတယ်။ Borrow checker က ဒီ graph ကို လျှောက်ကြည့်ပြီး reference တိုင်း valid ဖြစ်ကြောင်း၊ တခြားတစ်ယောက် ဖတ်နေတုန်း ဘယ် value ကိုမှ မရေးကြောင်း သက်သေပြပါတယ်။ Ownership error တွေ ဒီကလာတာပါ။
- Monomorphisation။ Generic code (
fn largest<T>) ကို သုံးတဲ့ တိကျတဲ့ type တစ်ခုစီအတွက် တစ်ခါစီ ကူးထုတ်လို့largest::<i32>နဲ့largest::<f64>က runtime type စစ်ဆေးမှု မပါတဲ့ သီးသန့် function နှစ်ခု ဖြစ်သွားပါတယ်။ Rust ရဲ့ generic တွေက run နေချိန်မှာ ဘာမှ မကုန်ကျတာ ဒါကြောင့်ပါ။ - LLVM နဲ့ linker။ MIR ကို Clang နဲ့ Swift သုံးတဲ့ optimiser back end ဖြစ်တဲ့ LLVM IR ဆီ ဘာသာပြန်ပြီး သင့် target (x86-64၊ ARM၊ WebAssembly…) အတွက် machine code ထုတ်ပါတယ်။ Linker က သင့် crate တွေကို binary တစ်ခုတည်းအဖြစ် ပေါင်းပါတယ်။
ဒီဇိုင်းရဲ့ တခြားအစိတ်အပိုင်း တချို့ကိုလည်း ကြိုသိထားသင့်ပါတယ်—
- Zero-cost abstraction တွေ။ Iterator၊ closure၊
Optionနဲ့ generic တွေက loop နဲ့ pointer တွေနဲ့ ကိုယ်တိုင် ရေးမယ့် machine code အတိုင်းပဲ compile ဖြစ်ပါတယ်။ အဆင့်မြင့်ပုံစံနဲ့ ရေးလို့ run နေချိန်မှာ ပိုမကုန်ပါဘူး။ - Crate နဲ့ Cargo။ Crate ဆိုတာ compile လုပ်တဲ့ ယူနစ်တစ်ခု၊ library ဒါမှမဟုတ် binary တစ်ခုပါ (npm package တစ်ခုလို့ မြင်ကြည့်ပါ)။ Cargo က crate တွေကို build၊ test၊ document နဲ့ publish လုပ်ပေးပြီး crates.io က registry ပါ။
std၊allocနဲ့core။ Standard library က အလွှာလိုက် လာပါတယ်။coreက operating system မပါတဲ့ microcontroller ပေါ်မှာတောင် ဘယ်နေရာမှာမဆို အလုပ်လုပ်ပါတယ်။allocကVec၊Stringလို heap type တွေ ထပ်ထည့်ပါတယ်။stdက file၊ thread နဲ့ networking ထပ်ထည့်ပါတယ်။ Embedded နဲ့ kernel code က အောက်အလွှာတွေကိုပဲ သုံးပါတယ်။- Edition တွေ။ နှစ်အနည်းငယ်တစ်ခါ (2015၊ 2018၊ 2021၊ 2024) edition တစ်ခုက syntax ရဲ့ breaking change အသေးလေးတွေကို စုထုတ်ပါတယ်။ Crate တစ်ခုစီက
Cargo.tomlထဲမှာ ကိုယ့် edition ကို ရွေးပြီး edition မတူတဲ့ crate တွေကလည်း လွတ်လပ်စွာ ချိတ်ဆက်သုံးလို့ ရလို့ Python 2 နဲ့ 3 လို ecosystem နှစ်ခြမ်း ကွဲတာ ဘယ်တော့မှ မဖြစ်ပါဘူး။ - Built-in runtime မပါတဲ့ async။ ဘာသာစကားက
async/awaitနဲ့Futuretrait ကို ပေးပေမဲ့ event loop မပေးပါဘူး။ Runtime library ကို ကိုယ်တိုင် ရွေးရပြီး server တွေအတွက် စံက Tokio ပါ။ JS promise တွေက ဖန်တီးလိုက်တာနဲ့ run စပေမဲ့ Rust future တွေက poll မလုပ်မချင်း ဘာမှ မလုပ်ပါဘူး။ - ကန့်သတ်ထားတဲ့ နယ်မြေအဖြစ်
unsafe။ Compiler မစစ်နိုင်တဲ့ လုပ်ဆောင်ချက် အနည်းငယ် (raw pointer တွေ၊ C ကို ခေါ်တာ) ကိုunsafe { }ထဲမှာ ရေးရပါတယ်။ Safe Rust ကို standard library ထဲက စစ်ဆေးပြီးသားunsafecore အသေးလေးတွေပေါ်မှာ တည်ဆောက်ထားတာပါ။
Rust ကို install လုပ်ခြင်း
တရားဝင် toolchain manager ဖြစ်တဲ့ rustup (Rust ရဲ့ nvm) နဲ့ install လုပ်ပါ — macOS ဒါမှမဟုတ် Linux မှာ rustup.rs က one-liner ကို run ပြီး Windows မှာ အဲဒီ site ကပဲ rustup-init.exe ကို download လုပ်ပါ။ Compiler rustc၊ package manager cargo၊ formatter နဲ့ linter ကို install လုပ်ပေးပါတယ်။ ပြီးရင် နေ့တိုင်း သုံးမယ့် command တွေ—
rustup update # update to the newest stable Rustcargo new hello # a new project with Cargo.toml and src/main.rscd hellocargo run # build in debug mode and runcargo check # type-check and borrow-check only, much fastercargo test # run every #[test] functioncargo clippy # the linter, like ESLintcargo fmt # the formatter, like Prettiercargo add serde --features derive # like npm install serdecargo build --release # an optimised binary in target/release/တစ်ကြောင်းချင်း ရှင်းလင်းချက်
- Rust က ခြောက်ပတ် တစ်ခါ stable version အသစ် ထုတ်ပါတယ်။
rustup updateက အဲဒီဆီ ရွှေ့ပေးပါတယ်။ Edition တစ်ခုအတွင်း stability ကို Rust က ကတိပေးထားလို့ code ဟောင်းတွေ ဆက် compile ဖြစ်ပါတယ်။ Cargo.toml(သင့်package.json)၊src/main.rs(entry point) နဲ့ git repository ပါတဲ့ folder တစ်ခု ဖန်တီးပါတယ်။ Library အတွက်ဆိုရင်cargo new --lib nameကို သုံးပါ။- Compile လုပ်ပြီး run ပါတယ်။ ပထမ build က dependency အားလုံးကို compile လုပ်လို့ နှေးပြီး နောက်ပိုင်း build တွေက incremental ပါ။ Build output က
dist/နေရာမှာ ရှိတဲ့target/ထဲ ရောက်ပြီး git ထဲ မထည့်ပါဘူး။ - Code generation ကလွဲပြီး အားလုံး run ပါတယ်။ ရေးနေတုန်း သုံးပါ၊
tsc --noEmitနဲ့ အနီးစပ်ဆုံးပါ။ rust-analyzer ပါတဲ့ editor တွေက save လုပ်တိုင်း run ပေးပါတယ်။ - သင့် code ထဲနဲ့
tests/ထဲက test တွေကို ရှာပြီး run ပါတယ်။ Install လုပ်စရာ Jest လည်း မရှိ၊ Vitest လည်း မရှိပါဘူး — testing က ဘာသာစကားနဲ့ Cargo ထဲမှာ ပါပြီးသားပါ။ - Clippy မှာ idiomatic မဖြစ်တဲ့ ဒါမှမဟုတ် နှေးတဲ့ code ကို ထောက်ပြပြီး များသောအားဖြင့် ပြင်နည်းပါ အကြံပြုတဲ့ lint ရာနဲ့ချီ ပါပါတယ်။ Reviewer တစ်ယောက်လို သဘောထားပါ။
- rustfmt က code ကို တရားဝင် style တစ်မျိုးတည်းနဲ့ format လုပ်ပါတယ်။ Rust project အားလုံးနီးပါး သုံးလို့ style အငြင်းပွားစရာ မရှိပါဘူး။
Cargo.tomlထဲကို dependency တစ်ခု ထည့်ပါတယ်။ Feature က crate ရဲ့ optional အပိုင်းတွေကို ဖွင့်ပေးတာပါ — serde ရဲ့derivefeature က#[derive(Serialize, Deserialize)]ကို ပေးပါတယ်။- Optimisation အပြည့်နဲ့ build လုပ်ပါတယ်။ Debug build တွေက release build ထက် ဆယ်ဆ ဒါမှမဟုတ် ပိုနှေးနိုင်လို့ benchmark ကို release build နဲ့ပဲ လုပ်ပါ။
Project ရဲ့ manifest က TOML file ပါ။ သင်သိပြီးသားနဲ့ ယှဉ်ကြည့်ရအောင်—
[package]name = "hello"version = "0.1.0"edition = "2024" [dependencies]serde = { version = "1.0", features = ["derive"] }tokio = { version = "1", features = ["full"] } [dev-dependencies]pretty_assertions = "1"တစ်ကြောင်းချင်း ရှင်းလင်းချက်
[package]table က သင့်package.jsonရဲ့ အပေါ်ပိုင်းပါ — name နဲ့ version (Cargo က npm လိုပဲ semver သုံးပါတယ်)။- ဒီ crate ကို ရေးထားတဲ့ edition ပါ။ Project အသစ်တွေက
2024ရပါတယ်။ Syntax rule အနည်းငယ်ကိုပဲ ပြောင်းပြီး ဘယ် crate တွေ သုံးလို့ရလဲဆိုတာကို ဘယ်တော့မှ မပြောင်းပါဘူး။ - Runtime dependency တွေ၊ package.json ထဲက
dependenciesလိုပါပဲ။ တိကျတဲ့ version တွေကိုpackage-lock.jsonနေရာမှာ ရှိတဲ့Cargo.lockထဲမှာ pin လုပ်ထားပါတယ် — application တွေအတွက် commit လုပ်ပါ။ "1.0"ဆိုတာ "1.0 ဒါမှမဟုတ် အသစ်၊ 2.0 အောက်" လို့ အဓိပ္ပာယ်ရပြီး npm ရဲ့^1.0နဲ့ အတူတူပါ။ Feature တွေကို dependency တစ်ခုချင်း ရွေးလို့ သုံးတာကိုပဲ compile လုပ်ပါတယ်။- Test၊ example နဲ့ benchmark တွေမှာပဲ သုံးတဲ့ dependency တွေ —
devDependenciesပေါ့။
Hello, Rust
Program တိုင်း fn main ကနေ စပါတယ်။ Run ကို နှိပ်ပြီး compile လုပ်ကာ run ကြည့်ပါ၊ ပြီးရင် name ကို ပြောင်းကြည့်ပါ။
Tab က indent · ⌘/Ctrl + Enter က run · Esc က editor ထဲက ထွက်
တစ်ကြောင်းချင်း ရှင်းလင်းချက်
fnက function ကြေညာတာဖြစ်ပြီး binary တစ်ခု စတာmainကနေပါ။ JS module လို top-level code မရှိပါဘူး — အားလုံးmainကနေ run ပါတယ်။ Block တွေက brace သုံးပြီး indentation က ထုံးစံအရ space လေးခုပါ။letက variable ဖန်တီးပါတယ်။ သူ့ type ဖြစ်တဲ့&str(borrow လုပ်ထားတဲ့ string) ကို TypeScript ကconst name = "Rust"ကို infer လုပ်သလိုပဲ infer လုပ်ပါတယ်။ Statement တွေ;နဲ့ ဆုံးပါတယ်။- Integer literal တစ်ခုပါ။ တခြား အချက်အလက် မရှိရင် Rust က 32-bit signed integer ဖြစ်တဲ့
i32ကို ရွေးပါတယ်။numbertype တစ်ခုတည်း မရှိပါဘူး — integer နဲ့ float က သီးခြားစီပါ။ println!က function မဟုတ်ဘဲ macro (!) ပါ — format string ကို compile time မှာ စစ်ပါတယ်။{name}က template literal ထဲက${name}လိုပဲ variable ကို ထည့်ပေးပါတယ်။ နာမည် စာလုံးပေါင်း မှားရင်undefinedမဟုတ်ဘဲ compile error ဖြစ်ပါတယ်။- ဗလာ
{}placeholder တွေကို အပို argument တွေနဲ့ အစဉ်လိုက် ဖြည့်ပါတယ်။{}က type ရဲ့ user-facingDisplayformat ကို သုံးပါတယ်။ - Method တွေကို JS လိုပဲ dot နဲ့ ခေါ်ပါတယ်။
to_uppercaseက ဒီ variable က ပိုင်တဲ့ heap ပေါ်က ကြီးထွားနိုင်တဲ့ string ဖြစ်တဲ့Stringအသစ် တစ်ခု return ပြန်ပါတယ်။ - Placeholder တွေကို ရောသုံးလို့ ရပါတယ်။
len()က အရှည်ကို byte နဲ့ ပြန်ပေးပါတယ် (စာသားက ASCII ဖြစ်လို့ ဒီမှာ စာလုံးအရေအတွက်နဲ့ တူပါတယ်)။
Variable၊ mutability နဲ့ shadowing
TypeScript မှာ const က variable ကို ပြန် assign မလုပ်ခိုင်းပေမဲ့ သူညွှန်ထားတဲ့ object ကိုတော့ ပြင်လို့ ရနေဆဲပါ။ Rust က ပိုတင်းကျပ်ပြီး ပိုရိုးရှင်းပါတယ် — mut လို့ မရေးမချင်း binding တစ်ခုက အောက်ဆုံးအထိ immutable ပါ။
const score = 10; // can't be reassignedlet lives = 3; // can belives -= 1;const user = { name: 'Ada' };user.name = 'Grace'; // allowed: const fixes the binding, not the objectတစ်ကြောင်းချင်း ရှင်းလင်းချက်
- JS မှာ
constနဲ့letကြား ရွေးရပြီး style guide အများစုကconstကို ပိုကြိုက်ပါတယ်။ - JS ရဲ့ အံ့သြစရာက ဒါပါ —
constက object ကို immutable မဖြစ်စေပါဘူး။ အဲဒီအတွက်Object.freezeဒါမှမဟုတ်Readonly<T>လိုပြီး အဲဒါတွေကတောင် shallow ပဲ ဖြစ်ပါတယ်။
Tab က indent · ⌘/Ctrl + Enter က run · Esc က editor ထဲက ထွက်
တစ်ကြောင်းချင်း ရှင်းလင်းချက်
- ရိုးရိုး
letကconstလိုပဲ immutable ပေမဲ့ အောက်ဆုံးအထိ ပါ —scoreက struct တစ်ခုဆိုရင် သူ့ field တွေထဲက ဘယ်တစ်ခုမှလည်း ပြောင်းလို့ မရပါဘူး။ //ကို ဖျက်ပြီး Run ကို နှိပ်ကာ compiler ငြင်းတာ ကြည့်ပါ။ Rust ရဲ့ error message တွေက အထောက်အကူဖြစ်တာ နာမည်ကြီးပါတယ် — ဒီတစ်ခုက စာကြောင်းနှစ်ကြောင်းလုံးကို ထောက်ပြပြီးmutထည့်ဖို့ အကြံပြုပါတယ်။let mutက binding ကို JS ရဲ့letလို mutable ဖြစ်စေပါတယ်။ Mutability က ထင်ရှားရှင်းလင်းလို့ ဖတ်သူက ဘယ် value တွေ ပြောင်းလဲလဲ တစ်ချက်ကြည့်ရုံနဲ့ သိနိုင်ပါတယ်။- Shadowing — ဒုတိယ
let inputက နာမည်တူ variable အသစ်တစ်ခု ဖန်တီးတာပါ၊ ဒီမှာ type မတူပါဘူး။ "Parse လုပ်ပြီး စာသားကို မေ့ပစ်" ဆိုတဲ့ အဆင့်တွေမှာ အသုံးများပါတယ်။parse()ကResultပြန်ပေးပြီးunwrap()က value ကို ထုတ်ယူပါတယ် (parse မအောင်မြင်ရင် crash ဖြစ်ပါတယ်၊ နောက်မှ ပိုပြောပါမယ်)။ constက compile-time constant ပါ။ Type ကို ထင်ရှားစွာ ရေးရပြီး SCREAMING_CASE နဲ့ ရေးကာ သုံးတဲ့နေရာတိုင်းမှာ inline လုပ်ပါတယ်။ JS ရဲ့constထက် config file ထဲ ထည့်မယ့် value နဲ့ ပိုနီးစပ်ပါတယ်။
Type တွေ — number၊ text၊ tuple နဲ့ array
JavaScript မှာ number (64-bit float) တစ်ခုနဲ့ bigint ပဲ ရှိပါတယ်။ Rust မှာတော့ size တစ်ခုစီအတွက် type တစ်ခုစီ ရှိလို့ value တစ်ခု memory ဘယ်လောက် ယူမလဲ၊ အနုတ်ဖြစ်နိုင်လား ဆိုတာ တိတိကျကျ ရွေးရပါတယ်။
Tab က indent · ⌘/Ctrl + Enter က run · Esc က editor ထဲက ထွက်
တစ်ကြောင်းချင်း ရှင်းလင်းချက်
- Signed integer တွေက
i8၊i16၊i32၊i64နဲ့i128ပါ —isizeက pointer တစ်ခုရဲ့ size ပါ။i32က default ပါ။ - Unsigned integer တွေ (
u8…u128၊usize) က အနုတ် မဖြစ်နိုင်ပါဘူး။u8တစ်ခုက 0 ကနေ 255 အထိ၊ byte တစ်ခု သိမ်းပါတယ်။usizeက length တွေနဲ့ index တွေရဲ့ type ပါ။ - Float တွေက
f32နဲ့f64ပါ —f64က JS number နဲ့ အတူတူပါ။_f64လို suffix က literal ရဲ့ type ကို သတ်မှတ်ပြီး_ကို ဂဏန်းတွေကြား ဘယ်နေရာမှာမဆို ခွဲဖို့ သုံးလို့ရပါတယ် (1_000_000)။ charတစ်ခုက Unicode scalar value တစ်ခု၊ လေး byte ဖြစ်ပြီး single quote နဲ့ ရေးလို့ မြန်မာ အက္ခရာ တစ်လုံးကcharတစ်ခုပါ။ String တွေက double quote သုံးပါတယ်။255 + 1ကu8ထဲ မဆံ့ပါဘူး။ Debug build မှာ ရိုးရိုး+က overflow ဖြစ်ရင် တိတ်တဆိတ် ပတ်မလည်ဘဲ panic ဖြစ် (program ရပ်) ပါတယ်။ Overflow ဖြစ်နိုင်ရင် ကိုယ်လိုချင်တာကို ပြောပါ —checked_addက overflow ဖြစ်ရင်Noneပြန်ပေးပြီးwrapping_addက 0 ဆီ ပတ်လည်ကာsaturating_addက အမြင့်ဆုံးမှာ ရပ်ပါတယ်။- Integer division က ဒသမကို ဖြတ်ပစ်လို့
7 / 2က3ဖြစ်ပြီး float နဲ့ဆိုရင်3.5ပါ။ JS မှာ နှစ်ခုလုံး3.5ရပါတယ်။ - Rust က number type တွေကို ကိုယ့်ဘာသာ ဘယ်တော့မှ မပြောင်းပေးပါဘူး၊
i32ကနေf64တောင် မပြောင်းပါဘူး။asက ထင်ရှားစွာ ပြောင်းပါတယ်။ အဲဒါမပါရင်a * cက compile error ပါ — type တွေ ရောတာက အမြဲတမ်း သင် ချရေးရတဲ့ ဆုံးဖြတ်ချက်ပါ။ - Tuple က type မတူတဲ့ value တွေကို TS tuple
[number, number]လို စုပေးပါတယ်။let (x, y)နဲ့ destructure လုပ်ပါ၊ ဒါမှမဟုတ်.0၊.1နဲ့ နေရာအလိုက် field ကို ဖတ်ပါ။ - Array တစ်ခုမှာ သူ့ type ရဲ့ အစိတ်အပိုင်းဖြစ်တဲ့ (
[&str; 3]) ပုံသေ length ရှိပြီး stack ပေါ်မှာ နေပါတယ်။ အဆုံးကျော်ပြီး index လုပ်ရင်undefinedပြန်မပေးဘဲ panic ဖြစ်ပါတယ်။ ကြီးထွားတဲ့ list အတွက် နောက်မှာ ပြမယ့်Vecကို သုံးပါ။
Function တွေ၊ ပြီးတော့ အရာအားလုံးက expression
နေ့စဉ် ရေးပုံမှာ အကြီးဆုံး ပြောင်းလဲမှု — Rust မှာ if၊ match နဲ့ block တစ်ခုလုံးအပါအဝင် အရာအားလုံးနီးပါးက value တစ်ခု ထုတ်ပေးတဲ့ expression ပါ။ Semicolon မပါဘဲ ရေးထားတဲ့ block ရဲ့ နောက်ဆုံး expression က အဲဒီ block ရဲ့ value ပါ။
Tab က indent · ⌘/Ctrl + Enter က run · Esc က editor ထဲက ထွက်
တစ်ကြောင်းချင်း ရှင်းလင်းချက်
- Parameter type တွေက strict TypeScript လိုပဲ မဖြစ်မနေ ရေးရပြီး နာမည်နောက်မှာ လာပါတယ်။
-> i32က return type ပါ။ TS နဲ့ မတူတာက Rust က function ရဲ့ signature ကို ဘယ်တော့မှ infer မလုပ်ပါဘူး — signature တွေက စာချုပ်ဖြစ်ပြီး inference က body ထဲမှာပဲ အလုပ်လုပ်ပါတယ်။ returnလည်း မပါ၊ semicolon လည်း မပါ — နောက်ဆုံး expression ကို return ပြန်ပါတယ်။;ထည့်လိုက်ရင် value က()(ဗလာ "unit" type၊ Rust ရဲ့void) ဖြစ်တဲ့ statement ဖြစ်သွားပြီး function က ဘာမှ return မပြန်ဘူးလို့ compiler က ညည်းပါလိမ့်မယ်။returnကတော့ စောစော ထွက်ဖို့ ရှိပါတယ်။&'static strက program တစ်ခုလုံး အသက်ရှင်နေတဲ့ string slice ပါ — string literal တွေကို binary ထဲမှာပဲ သိမ်းထားလို့ အမြဲ valid ဖြစ်ပါတယ်။ifက expression ဖြစ်လို့ JS ရဲ့ ternary operator လို အလုပ်လုပ်ပါတယ်။ Branch တိုင်းမှာ type တူရပြီး condition က တကယ့်boolဖြစ်ရပါမယ် — truthiness မရှိလို့if 1 { }က compile မဖြစ်ပါဘူး။- Brace ထဲက block ကလည်း expression ပါ။ ယာယီ
wနဲ့hက အထဲမှာပဲ ရှိပြီး သူ့ value ကw * hပါ။ Helper variable တွေ အပြင်မပေါက်ဘဲ အဆင့်အနည်းငယ်နဲ့ value တစ်ခု တွက်ဖို့ သပ်ရပ်တဲ့ နည်းပါ။
Control flow — loop တွေနဲ့ match
Tab က indent · ⌘/Ctrl + Enter က run · Esc က editor ထဲက ထွက်
တစ်ကြောင်းချင်း ရှင်းလင်းချက်
loopကbreakမတွေ့မချင်း အဆုံးမရှိ ထပ်ခါထပ်ခါ လုပ်ပါတယ်။while (true)ကို အစားထိုးပြီး expression ဖြစ်လို့ value ထုတ်ပေးနိုင်ပါတယ်။break countက loop ထဲက ထွက်ရုံသာမကcountကို loop ရဲ့ value အဖြစ် ထုတ်ပေးလို့foundက 8 ဖြစ်လာပါတယ်။ JS မှာဆိုရင် loop အပြင်မှာ ကြေညာထားတဲ့ variable တစ်ခု လိုပါလိမ့်မယ်။whileက JS လိုပဲ အလုပ်လုပ်ပြီး condition ပတ်လည်မှာ parentheses မလိုပါဘူး။print!က newline မပါတဲ့println!ပါ။forက iterate လုပ်လို့ရတဲ့ ဘာကိုမဆို loop ပတ်ပါတယ်။1..=3က အဆုံးပါဝင်တဲ့ range (1၊ 2၊ 3) ဖြစ်ပြီး1..3က အဆုံးကို မပါပါဘူး (1၊ 2)။ C-stylefor (let i = 0; i < n; i++)မရှိပေမဲ့ သိပ်မလွမ်းရပါဘူး။.iter().enumerate()က(index, item)အတွဲတွေ ထုတ်ပေးပြီး loop head မှာ destructure လုပ်ပါတယ်၊for (const [i, lang] of langs.entries())လိုပေါ့။matchက superpower ပါတဲ့switchပါ — expression ဖြစ်ပြီး arm တွေက range၊ tuple၊ enum နဲ့ condition တွေကို match လုပ်နိုင်ကာ fall-through မရှိလို့ မေ့စရာbreakမရှိပါဘူး။_က ဘာကိုမဆို match လုပ်ပါတယ်။matchက exhaustive ဖြစ်ရပါမယ် — ဒီ arm ကို ဖယ်လိုက်ရင် ဘယ်ဂဏန်းတွေ မပါသေးလဲ compiler က ပြောပါလိမ့်မယ်။ နောက်ပိုင်း enum တွေပေါ်မှာmatchက အသုံးဝင်တာ ဒီစစ်ဆေးမှုကြောင့်ပါ။
Ownership — အရာအားလုံး အခြေခံတဲ့ idea
ဒါက Rust ရဲ့ နှလုံးသားဖြစ်ပြီး JS မှာ တူညီတာ မရှိတဲ့ အပိုင်းပါ။ အရင်ဆုံး JavaScript က object တွေကို ဘယ်လို ဆက်ဆံလဲ ပြန်သတိရကြည့်ပါ—
const a = { tags: ['ts'] };const b = a; // b and a point at the same objectb.tags.push('rust');console.log(a.tags); // ['ts', 'rust']: changed through bတစ်ကြောင်းချင်း ရှင်းလင်းချက်
- Object တစ်ခုကို assign လုပ်ရင် reference ကို ကူးပါတယ်။ အခု နာမည်နှစ်ခုက object တစ်ခုတည်းကို မျှသုံးနေပြီး garbage collector က ဘယ်နာမည်ကမှ မရောက်နိုင်တော့မှ free လုပ်ပါတယ်။
- နာမည်တစ်ခုကနေ ပြင်တာကို နောက်တစ်ခုကနေ မြင်ရပါတယ်။ ဒီ sharing က အဆင်ပြေသလို bug အများကြီးရဲ့ အမြစ်လည်း ဖြစ်ပါတယ် — သင်ခေါ်တဲ့ function က reference ကို သိမ်းထားပြီး နောက်မှ သင့် data ကို ပြင်နိုင်ပါတယ်။
Rust က ဒါကို compiler စစ်ဆေးတဲ့ rule သုံးခုနဲ့ အစားထိုးပါတယ်—
- Value တိုင်းမှာ owner တစ်ယောက်တည်း၊ variable တစ်ခု ရှိပါတယ်။
- Owner က scope ထဲက ထွက်သွားရင် value ကို အဲဒီနေရာမှာပဲ drop လုပ် (memory ကို free လုပ်) ပါတယ်။ Garbage collector မလိုပါဘူး။
- Heap value တစ်ခုကို assign ဒါမှမဟုတ် pass လုပ်ရင် ownership ကို နေရာအသစ်ဆီ move လုပ်ပါတယ်။ နာမည်ဟောင်းကို ဆက်သုံးလို့ မရတော့ပါဘူး။
Tab က indent · ⌘/Ctrl + Enter က run · Esc က editor ထဲက ထွက်
တစ်ကြောင်းချင်း ရှင်းလင်းချက်
String::fromက စာသားကို heap ပေါ်တင်ပြီးaကို owner ဖြစ်စေပါတယ်။::က type တစ်ခုနဲ့ သက်ဆိုင်တဲ့ function ကို static method လို ခေါ်တာပါ။- ဒါက shared reference မဟုတ်ဘဲ move ပါ။ Ownership က
bဆီ ကူးသွားပြီးaကို သုံးလို့ မရတော့ပါဘူး။ Pointer၊ length နဲ့ capacity ကိုပဲ ကူးပြီး စာသားကိုယ်တိုင်က နေရာမှာပဲ ရှိနေပါတယ်။ - ဒီစာကြောင်းကို uncomment လုပ်ပြီး Run ပါ — move ပြီးနောက်
aကို သုံးတာကို compiler က ငြင်းပါတယ်။ JS မှာဆိုရင် object ကို တိတ်တဆိတ် မျှသုံးမှာဖြစ်ပြီး Rust မှာတော့ free လုပ်ဖို့ owner တစ်ယောက်ထက် ဘယ်တော့မှ မပိုပါဘူး။ .clone()က ထင်ရှားတဲ့ deep copy လုပ်လို့bနဲ့cက string သီးခြားစီကို ပိုင်ပါတယ်။ Clone တွေက code ထဲမှာ မြင်ရလို့ ဘယ်နေရာမှာ ကူးတာ ကုန်ကျလဲ မြင်နိုင်ပါတယ်။- ပုံသေ size ရှိတဲ့ type အသေးလေးတွေ (integer၊ float၊
bool၊charနဲ့ အဲဒါတွေရဲ့ tuple) ကCopyပါ — assign လုပ်ရင် bit တွေကို ကူးပြီး နာမည်နှစ်ခုလုံး ဆက်သုံးလို့ ရပါတယ်။ Move က heap memory လို တစ်ခုခုကို ပိုင်တဲ့ value တွေအတွက်ပဲ အရေးပါပါတယ်။ Stringကို function ဆီ pass လုပ်ရင် parametersထဲ move ဖြစ်သွားပါတယ်။takereturn ပြန်တဲ့အခါsကို drop လုပ်ပြီး memory ကို free လုပ်ပါတယ်။ ဒီစာကြောင်းနောက်cကို သုံးရင် error ဖြစ်ပါလိမ့်မယ်။- Value ကို return ပြန်ရင် ownership က ခေါ်သူဆီ move ဖြစ်လို့
giveက string ဖန်တီးပြီးdက သူ့ owner ဖြစ်လာပါတယ်။ - Closing brace မှာ
bနဲ့dက scope ထဲက ထွက်ပြီး သူတို့ string တွေကို ဖန်တီးခဲ့တဲ့ အစဉ်ရဲ့ ပြောင်းပြန်နဲ့ free လုပ်ပါတယ်။ ဒါက ကြိုတင်သိနိုင်ပါတယ် — memory ဘယ်အချိန် လွှတ်လဲ (file တွေ၊ socket တွေကိုလည်း ဒီနည်းအတိုင်း free လုပ်လို့ ဘယ်အချိန် ပိတ်လဲ) တိတိကျကျ သိပါတယ်။
Borrowing — ownership မယူဘဲ reference တွေ
အရာအားလုံးကို function တွေထဲ move လုပ်နေရရင် ပင်ပန်းမှာပါ။ အဲဒီအစား borrow လုပ်ပါတယ် — &value က function ကို ဖတ်ဖို့ reference ပေးပြီး &mut value က ပြင်ဖို့ reference ပေးပါတယ်။ Borrow checker က rule တစ်ခုတည်းကို ကျင့်သုံးပါတယ် — ဘယ်အချိန်မှာမဆို shared & reference ဘယ်နှစ်ခုမဆို ဒါမှမဟုတ် &mut reference တစ်ခုတည်း ရှိနိုင်ပြီး နှစ်မျိုးလုံး တစ်ပြိုင်နက် ဘယ်တော့မှ မရှိရပါဘူး။
Tab က indent · ⌘/Ctrl + Enter က run · Esc က editor ထဲက ထွက်
တစ်ကြောင်းချင်း ရှင်းလင်းချက်
&Stringက shared reference ပါ —lengthက string ကို ဖတ်လို့ရပေမဲ့ ပြင်လို့ မရဘဲ ပိုင်လည်း မပိုင်လို့ return ပြန်တဲ့အခါ ဘာမှ free မလုပ်ပါဘူး။ (Idiomatic code မှာ ဒီနေရာမှာ&strကို ယူပါတယ်၊ နောက်အပိုင်းမှာ ကြည့်ပါ။)&mut Stringက mutable reference ပါ —shoutက ခေါ်သူရဲ့ string ကို နေရာမှာပဲ ပြင်နိုင်ပါတယ်။&wordက call ကြာသရွေ့ string ကို ငှားပေးပါတယ်။ ပြီးရင်wordက ကျွန်တော်တို့ ဆီမှာပဲ ရှိနေဆဲပါ။- Mutable အနေနဲ့ ငှားဖို့ variable (
let mut word) နဲ့ borrow (&mut word) နှစ်ခုလုံးက ပြောရပါမယ်။ ဘယ် function တွေက သူတို့ data ကို ပြင်နိုင်လဲဆိုတာ JS က ဘယ်တော့မှ မပြောပေမဲ့ ဒီမှာ call site မှာတင် ဖတ်သူ မြင်နိုင်ပါတယ်။ - ဖတ်သူတွေက တစ်ယောက်နဲ့တစ်ယောက် မနှောင့်ယှက်နိုင်လို့ shared borrow ဘယ်နှစ်ခုမဆို အတူ ရှိနိုင်ပါတယ်။
r1နဲ့r2ကိုprintln!နောက်မှာ မသုံးတော့လို့ ဒီမှာ mutable borrow ခွင့်ပြုပါတယ်။ Compiler က reference တစ်ခုချင်းကို ကြေညာထားတဲ့ block ကိုပဲ မဟုတ်ဘဲ နောက်ဆုံး ဘယ်မှာ သုံးလဲဆိုတာကို လိုက်ကြည့်ပါတယ် (ဒါကို non-lexical lifetimes လို့ ခေါ်ပါတယ်)။
အခု borrow checker က "မရဘူး" လို့ ပြောတာကို တွေ့ကြည့်ရအောင်။ ဒီ program က တမင် compile မဖြစ်အောင် ရေးထားတာပါ။ Run ကို နှိပ်ပြီး error ကို ဖတ်ကြည့်ပါ၊ ပြီးရင် မှတ်စုထဲက ပြင်နည်းကို စမ်းကြည့်ပါ။
Tab က indent · ⌘/Ctrl + Enter က run · Esc က editor ထဲက ထွက်
တစ်ကြောင်းချင်း ရှင်းလင်းချက်
firstက vector ရဲ့ ပထမ element ကို borrow လုပ်ပါတယ်၊ vector ရဲ့ heap buffer ထဲကို ညွှန်တဲ့ pointer တစ်ခုပါ။pushက&mut listလိုပါတယ်။ Buffer ပြည့်နေရင်pushက ပိုကြီးတဲ့ buffer အသစ် allocate လုပ်ပြီး element တွေကို ကူးကာ buffer ဟောင်းကို free လုပ်ပါတယ်။ ဒါဆိုရင်firstက free လုပ်ပြီးသား memory ကို ညွှန်နေမှာပါ — C မှာ ဒါက ဂန္ထဝင် use-after-free bug ပါ။pushနောက်မှာfirstကို သုံးနေလို့ shared borrow နဲ့ mutable borrow ထပ်နေပြီး compiler က ငြင်းပါတယ် (error E0502)။ ပြင်ဖို့let first = list[0];နဲ့ value ကို ကူးထုတ်ပါ (i32ကCopyပါ)၊ ဒါမှမဟုတ်println!ကိုpushအပေါ် ရွှေ့ပါ။
String နဲ့ &str
Rust မှာ အဓိက string type နှစ်ခု ရှိပြီး အစပိုင်းမှာ လူတိုင်း ရှုပ်ကြပါတယ်။ String က StringBuilder လို ပိုင်ဆိုင်ထားတဲ့၊ ကြီးထွားနိုင်တဲ့ buffer ပါ။ &str ("string slice") ကတော့ တခြားတစ်ယောက် ပိုင်တဲ့ စာသားထဲကို borrow လုပ်ထားတဲ့ မြင်ကွင်းပါ — literal တစ်ခု၊ String ရဲ့ အစိတ်အပိုင်း ဒါမှမဟုတ် တစ်ခုလုံး။ နှစ်ခုလုံး အမြဲ valid UTF-8 ပါ။
Tab က indent · ⌘/Ctrl + Enter က run · Esc က editor ထဲက ထွက်
တစ်ကြောင်းချင်း ရှင်းလင်းချက်
- ဖတ်ရုံပဲ ဖတ်မယ့် parameter တွေအတွက်
&strကို ယူပါ — literal၊Stringနဲ့ slice အားလုံးကို လက်ခံပါတယ်။ စာသားအသစ် တည်ဆောက်တဲ့အခါ ခေါ်သူက ပိုင်ဖို့ လိုလို့Stringကို return ပြန်ပါ။ format!ကprintln!လိုပဲ အလုပ်လုပ်ပေမဲ့ print မထုတ်ဘဲStringကို return ပြန်ပါတယ်။ Rust ရဲ့ template literal ပါ။- Literal တစ်ခုက program binary ထဲကို ညွှန်တဲ့
&strပါ။ Stringက heap ပေါ်မှာ နေပြီး ကြီးထွားနိုင်ပါတယ်။String::from(…)၊"…".to_string()နဲ့"…".to_owned()အားလုံး တစ်ခု ဖန်တီးပေးပါတယ်။push_strက နေရာမှာပဲ ဆက်ထည့်လို့ownedကmutဖြစ်ရတာပါ။ Emoji တွေ ရပါတယ် — string တွေက UTF-8 ပါ။&ownedကStringကို borrow လုပ်ပြီး Rust က&Stringကို&strအဖြစ် အလိုအလျောက် ပြောင်းပေးပါတယ် (deref coercion)။&strparameter တွေက နှစ်မျိုးလုံး လက်ခံတာ ဒါကြောင့်ပါ။- Slicing က byte range ကို ယူပါတယ် —
0..5က "Minga" ပါ။ Byte အများကြီးနဲ့ ဖွဲ့ထားတဲ့ character အလယ်မှာ slice လုပ်ရင် panic ဖြစ်လို့ ASCII မဟုတ်တဲ့ စာသားအတွက် ပုံသေ ဂဏန်းတွေအစားchars()ဒါမှမဟုတ် ရှာဖွေမှုနဲ့ အလုပ်လုပ်ပါ။ - မြန်မာ character တွေက UTF-8 မှာ တစ်လုံးကို သုံး byte စီ ယူလို့
len()(byte) နဲ့chars().count()(Unicode scalar value) မတူပါဘူး — 27 နဲ့ 9 ပါ။ JavaScript ရဲ့.lengthက UTF-16 code unit တွေကို ရေတွက်လို့ တတိယ အဖြေတစ်ခု ထပ်ရပါတယ်။ ကိုယ်တကယ် ဆိုလိုတာကို ရေတွက်ပါ။
Lifetime တွေ အကျဉ်းချုပ်
Lifetime ဆိုတာ "reference တစ်ခု ဘယ်လောက်ကြာကြာ valid ဖြစ်နေလဲ" ဆိုတာအတွက် compiler ပေးတဲ့ နာမည်ပါ။ အများအားဖြင့် ကိုယ့်ဘာသာ တွက်ထုတ်ပါတယ်။ Function တစ်ခုက reference ကို return ပြန်ပြီး ဘယ် input ကလာလဲ compiler မသိနိုင်တဲ့အခါမှပဲ ရေးရပါတယ်။
Tab က indent · ⌘/Ctrl + Enter က run · Esc က editor ထဲက ထွက်
တစ်ကြောင်းချင်း ရှင်းလင်းချက်
<'a>က generic လို lifetime parameter တစ်ခု ကြေညာပါတယ်။ Signature က ပြောတာက — result ကxနဲ့yထဲက သက်တမ်းတိုတဲ့ တစ်ခုထက် ပိုမကြာပါဘူး။ ဘယ်အရာ ဘယ်လောက်ကြာကြာ အသက်ရှင်လဲကို မပြောင်းပါဘူး — ခေါ်သူကို စစ်နိုင်အောင် ဆက်နွယ်မှုကို ဖော်ပြတာပါ။- Reference ကို ကိုင်ထားတဲ့ struct က အဲဒီ reference ဘယ်လောက်ကြာ အသက်ရှင်လဲ ပြောရပါတယ် —
Excerptတစ်ခုက သူညွှန်ထားတဲ့ စာသားထက် ပိုကြာကြာ မနေနိုင်ပါဘူး။ bက ဒီ အတွင်း block ထဲမှာပဲ ရှိပြီး closing brace မှာ free ဖြစ်ပါတယ်။longestကbထဲကို reference ပြန်ပေးနိုင်လို့ အဲဒီ reference ကို brace ကျော်ပြီး ထိန်းထားရင် error ဖြစ်ပါလိမ့်မယ်။.to_string()က result ကို ဆက်ရှင်သန်နိုင်တဲ့ ownedStringအသစ်ထဲ ကူးပါတယ်။ Error ကို မြင်ဖို့.to_string()ကို ဖယ်ကြည့်ပါ။splitကtextကနေ borrow လုပ်ထားတဲ့&strslice တွေကို ကူးစရာမလိုဘဲ ထုတ်ပေးပါတယ်။next()က ပထမတစ်ခုကိုOptionအဖြစ် ယူပြီးunwrap()က ထုတ်ယူပါတယ်။
Struct တွေနဲ့ method တွေ
Rust မှာ class မရှိပါဘူး။ Data ကို struct ထဲ ထည့်ပြီး behaviour ကို impl block တွေထဲ ထည့်ပါတယ်။ Inheritance မရှိပါဘူး — behaviour မျှသုံးတာက အောက်မှာ ပြောမယ့် trait တွေရဲ့ အလုပ်ပါ။
class Account { constructor(public owner: string, private balance = 0) {} deposit(amount: number) { this.balance += amount; } getBalance() { return this.balance; }}Tab က indent · ⌘/Ctrl + Enter က run · Esc က editor ထဲက ထွက်
တစ်ကြောင်းချင်း ရှင်းလင်းချက်
#[derive]က compiler ကို standard trait implementation တွေ ထုတ်ပေးဖို့ တောင်းတာပါ —{:?}နဲ့ print ထုတ်ဖို့Debug၊.clone()အတွက်Cloneနဲ့==အတွက်PartialEq။ JS မှာ object ပေါ်က==က reference တွေကို နှိုင်းယှဉ်ပေမဲ့ Rust မှာတော့ အကြောင်းအရာကို နှိုင်းယှဉ်ပြီး သင်တောင်းမှပဲ ရပါတယ်။- Struct ဆိုတာ နာမည်ပေးထားတဲ့ type ပါ field အစုတစ်ခုပါ၊ memory layout ကိုပါ ဆုံးဖြတ်တဲ့ TS
interfaceလိုပေါ့။pubလို့ မမှတ်ထားရင် field တွေက module အတွက် private ပါ။ - ငွေကို အသေးဆုံး ယူနစ် (cent၊ ပြား) နဲ့ unsigned integer အဖြစ် သိမ်းပါ — အနုတ် မဖြစ်နိုင်ဘဲ float ရဲ့ rounding အံ့သြစရာတွေ မရှိပါဘူး။
implblock က type တစ်ခုရဲ့ method တွေကို ကိုင်ထားပါတယ်။ Crate တစ်ခုတည်းရဲ့ file မတူတာတွေထဲမှာတောင် အများကြီး ရှိနိုင်ပါတယ်။selfမပါတဲ့ function က associated function ဖြစ်ပြီး Rust ရဲ့staticmethod ပါ။newက keyword မဟုတ်ဘဲ ထုံးစံတစ်ခုပဲ ဖြစ်ပါတယ် — constructor မရှိပါဘူး။Selfက implement လုပ်နေတဲ့ type ကို ဆိုလိုပါတယ်။&mut selfက method က struct ကို mutable အနေနဲ့ borrow လုပ်တာမို့mutvariable ပေါ်မှာပဲ ခေါ်လို့ ရပါတယ်။ ဘယ် method တွေက value ကို ပြောင်းလဲလဲ signature က ပြောပါတယ်။&selfက read-only borrow လုပ်ပါတယ်။ Field နဲ့ method က နာမည်တူနိုင်ပါတယ် —acc.balance()က method ကို ခေါ်ပြီးacc.balanceက field ကို ဖတ်ပါတယ်။- Associated function တွေကို
::နဲ့၊ method တွေကို.နဲ့ ခေါ်ပါ။acc.deposit(…)အတွက် Rust က&mut accကို အလိုအလျောက် borrow လုပ်ပေးပါတယ်။ {:?}(ဒီမှာ{acc:?}) ကDebugပုံစံကို print ထုတ်ပါတယ်၊ object တစ်ခုကိုconsole.logလုပ်သလို develop လုပ်နေတုန်း အသုံးဝင်ပါတယ်။{:#?}က စာကြောင်းအများကြီးနဲ့ လှလှပပ print ထုတ်ပါတယ်။- Struct update syntax
..acc.clone()က ကျန်တဲ့ field တွေကို တခြား value တစ်ခုကနေ ဖြည့်ပါတယ်၊ JS ရဲ့{ ...acc, owner: "Grace" }လိုပေါ့။ မ clone ရင် spread ကaccရဲ့ field တွေကို move လုပ်သွားမှာမို့ clone လုပ်ပါတယ်။
Enum တွေနဲ့ pattern matching
TypeScript မှာ discriminated union တွေ သုံးဖူးရင် Rust enum တွေကို နားလည်ပြီးသားပါ။ Variant တစ်ခုစီက ကိုယ်ပိုင် data ကို သယ်နိုင်ပြီး match က တစ်ခုချင်းစီကို ကိုင်တွယ်ကြောင်း သေချာစေပါတယ်။
type Shape = | { kind: 'circle'; radius: number } | { kind: 'rect'; w: number; h: number }; function area(s: Shape): number { switch (s.kind) { case 'circle': return Math.PI * s.radius ** 2; case 'rect': return s.w * s.h; }}တစ်ကြောင်းချင်း ရှင်းလင်းချက်
- TypeScript က
kindtag ပေါ်မှာ narrow လုပ်ပါတယ်။ ကျန်နေတဲ့ case ကို ဖမ်းမဖမ်းက သင့် compiler setting နဲ့ return type ပေါ် မူတည်ပါတယ် — Rust မှာတော့ အမြဲ ဖမ်းပါတယ်။
Tab က indent · ⌘/Ctrl + Enter က run · Esc က editor ထဲက ထွက်
တစ်ကြောင်းချင်း ရှင်းလင်းချက်
enumက value တစ်ခုရဲ့ ဖြစ်နိုင်တဲ့ ပုံစံအားလုံးကို စာရင်းပြုစုပါတယ်။ Tag က ပါပြီးသားမို့kindfield ကို ကိုယ်တိုင် ဘယ်တော့မှ မရေးရပါဘူး။- Variant တစ်ခုက struct လို နာမည်ပါ field တွေ၊ tuple လို နာမည်မပါ field တွေ (
Circle(f64)) ဒါမှမဟုတ် ဘာမှမပါဘဲ သယ်နိုင်ပါတယ်။ - Data မပါတဲ့ variant က TS string-literal union ရဲ့ member တစ်ခုလိုပါ။
matchက variant တစ်ခုစီကို destructure လုပ်ပြီး သူ့ field တွေကို variable အဖြစ် bind လုပ်ပါတယ်။ Exhaustive ဖြစ်ပါတယ် —Trianglevariant တစ်ခု ထည့်လိုက်ရင် အဲဒါကို မေ့ထားတဲ့matchတိုင်း compile မဖြစ်တော့ပါဘူး။ "Case အသစ်ကို ကိုင်တွယ်ဖို့ မေ့သွားတယ်" ဆိုတာကို production bug ကနေ compile error အဖြစ် ပြောင်းပေးပါတယ်။- Enum တွေရဲ့ array ပါ။ Variant တွေက မတူတဲ့ data သယ်ပေမဲ့ element တိုင်းမှာ type တူ —
Shapeပါ။ {:.2}က float ကို ဒသမ နှစ်နေရာနဲ့ print ထုတ်ပါတယ်၊toFixed(2)လိုပေါ့။matchက enum တွေတင်မက ဘယ် value ပေါ်မှာမဆို အလုပ်လုပ်ပါတယ်။- Match guard (
if …) က arm တစ်ခုကို condition ထပ်ထည့်ပါတယ်။ Guard က သုံးနိုင်အောင်xက match ဖြစ်တဲ့ value ကို bind လုပ်ပါတယ်။
null နဲ့ undefined အစား Option
Tony Hoare က null reference တွေကို သူ့ရဲ့ "ဒေါ်လာ ဘီလီယံတစ်ထောင်တန် အမှား" လို့ ခေါ်ခဲ့ပါတယ်။ Rust မှာ null မရှိပါဘူး။ မရှိနိုင်တဲ့ value မှာ Some(value) နဲ့ None ဆိုတဲ့ variant နှစ်ခုပါတဲ့ enum ဖြစ်တဲ့ Option<T> type ရှိပြီး value ကို မသုံးခင် None ကို ကိုင်တွယ်ရပါတယ်။
Tab က indent · ⌘/Ctrl + Enter က run · Esc က editor ထဲက ထွက်
တစ်ကြောင်းချင်း ရှင်းလင်းချက်
- User မရှိနိုင်ဘူးဆိုတာကို TS မှာ
string | undefinedက ပြောသလို return type က ရိုးရိုးသားသား ပြောပါတယ်။ ကွာခြားချက်ကOption<&str>က&strမဟုတ်လို့ string method တွေကို မတော်တဆ ခေါ်လို့ မရပါဘူး။ - Variant နှစ်ခုလုံးကို match လုပ်တာက ကိုင်တွယ်ဖို့ အထင်ရှားဆုံး နည်းပါ။
if letက narrowing ပါတဲ့if (user !== undefined)လို ကိုယ်ဂရုစိုက်တဲ့ case ကိုပဲ ကိုင်တွယ်ပါတယ်။unwrap_orက JS ရဲ့?? "guest"လို default ပေးပါတယ်။mapကSomeထဲက value ကို ပြောင်းပြီးNoneကို မထိပါဘူး၊ optional chaininguser?.lengthလိုပေါ့။|n| n.len()က closure ဖြစ်ပြီး Rust ရဲ့ arrow function(n) => n.lengthပါ။Some(3)ကို print ထုတ်ပါတယ် —Debugပုံစံက wrapper ကို ပြပါတယ်။let … elseက unwrap လုပ် ဒါမှမဟုတ် ထွက်ပါတယ် — pattern မကိုက်ရင်elseblock က ထွက်ရပါမယ် (return၊breakဒါမှမဟုတ် panic)။ စောစော return ပြန်တဲ့ guard clause ရဲ့ Rust version ပါ။
Result နဲ့ ? သုံးပြီး error ကိုင်တွယ်ခြင်း
JS မှာ ဘယ် function မဆို throw လုပ်နိုင်ပြီး သူ့ signature ထဲမှာ ဘာမှ မပြောပါဘူး။ Rust မှာ fail ဖြစ်နိုင်တဲ့ function က Ok(value) ဒါမှမဟုတ် Err(error) ဆိုတဲ့ enum ဖြစ်တဲ့ Result<T, E> ကို return ပြန်ပါတယ်။ ? operator က error တွေကို character တစ်လုံးတည်းနဲ့ အပေါ်ဆီ လွှဲပေးလို့ exception ရဲ့ အဆင်ပြေမှုကို error တွေ type ထဲမှာ စာရင်းပါတဲ့ ပုံစံနဲ့ ရပါတယ်။
function parsePort(s: string): number { const n = Number(s); if (!Number.isInteger(n) || n < 1 || n > 65535) throw new Error('bad port: ' + s); return n;}တစ်ကြောင်းချင်း ရှင်းလင်းချက်
parsePortရဲ့ type ထဲမှာ throw လုပ်နိုင်ကြောင်း ဘာမှ ခေါ်သူကို မပြောလို့try/catchကို မေ့ဖို့ လွယ်ပြီး run နေချိန်မှပဲ ပေါ်လာပါတယ်။
Tab က indent · ⌘/Ctrl + Enter က run · Esc က editor ထဲက ထွက်
တစ်ကြောင်းချင်း ရှင်းလင်းချက်
useကimportလို နာမည်တွေကို scope ထဲ ယူလာပါတယ်။std::fmtက module path ဖြစ်ပြီး::က သူ့အစိတ်အပိုင်းတွေကို ခွဲပါတယ်။- ကိုယ်ပိုင် error type က ဒီ code fail ဖြစ်နိုင်တဲ့ နည်းလမ်းတိုင်းကို စာရင်းပြုစုထားတဲ့ enum ပါ။ Variant တစ်ခုက standard library ရဲ့ parse error ကို ထုပ်ထားပြီး နောက်တစ်ခုက မှားနေတဲ့ ဂဏန်းကို သယ်ပါတယ်။
Displaytrait ကို implement လုပ်တာက error ကို{}နဲ့ ဘယ်လို print ထုတ်မလဲ ဆုံးဖြတ်ပါတယ်။write!က formatter ဆီ ချိန်ထားတဲ့format!ပါ။ (တကယ့် project တွေမှာ thiserror crate က variant တစ်ခုစီအတွက် attribute တစ်ခုနဲ့ ဒီ impl ကို ထုတ်ပေးပါတယ်။)Fromimpl ကParseIntErrorကို ကျွန်တော်တို့ConfigErrorအဖြစ် အလိုအလျောက် ဘယ်လို ပြောင်းမလဲ?ကို ပြောပါတယ်။- Signature က "
u16တစ်ခု ဒါမှမဟုတ်ConfigErrorတစ်ခု" လို့ ပြောပါတယ်။ ခေါ်သူတွေက နှစ်ခုလုံးကို ကိုင်တွယ်ရပါမယ် —Resultကို လျစ်လျူရှုရင် compiler warning ပေးပါတယ်။ ?operator —parse()ကErrပြန်ရင် error ကိုFromနဲ့ ပြောင်းပြီး ဒီ function ကနေ ချက်ချင်း return ပြန် ပါတယ်၊Okဆိုရင် value ကို ထုတ်ယူပါတယ်။try/catch/rethrowတစ်ခုလုံးကို အစားထိုးပါတယ်။ Target typeu32ကparseကို ဘာအဖြစ် parse လုပ်ရမလဲ ပြောပါတယ်။- Error ကို စောစော return ပြန်တာက ရိုးရိုး control flow ပါ —
Err(…)က value တစ်ခုပဲ ဖြစ်ပါတယ်။ - အောင်မြင်ရင်
Okနဲ့ ထုပ်ပါတယ်။ အပေါ်မှာ range ကို စစ်ပြီးသားမို့as u16က safe ဖြစ်ပါတယ်။ Resultပေါ်မှာ match လုပ်ရင် ရလဒ်နှစ်မျိုးလုံးကို ကိုင်တွယ်ပါတယ်။ Input သုံးခုက လမ်းကြောင်း မတူတာကို သတိထားကြည့်ပါ။Errဖြစ်ရင် crash ဖြစ်တဲ့unwrap()နဲ့expect("message")လည်း ရှိပါတယ် — ဥပမာတွေ၊ test တွေမှာ ရပေမဲ့ production လမ်းကြောင်းမှာတော့ code smell ပါ။
Trait တွေနဲ့ generic တွေ
Trait ဆိုတာ type တစ်ခုက ရှိမယ်လို့ ကတိပြုတဲ့ method အစုပါ၊ TS interface လိုပေါ့။ ကွာခြားချက် — TS မှာ ပုံစံမှန်တဲ့ object ဘယ်ဟာမဆို interface နဲ့ ကိုက်ပါတယ် (structural typing)၊ Rust မှာတော့ တစ်ယောက်ယောက်က impl Trait for Type လို့ ရေးမှပဲ type က trait ကို implement လုပ်ပါတယ် (nominal typing)။ ကိုယ့် trait တွေကို သူများ type တွေအတွက်၊ သူများ trait တွေကို ကိုယ့် type တွေအတွက် implement လုပ်နိုင်ပါတယ်။
Tab က indent · ⌘/Ctrl + Enter က run · Esc က editor ထဲက ထွက်
တစ်ကြောင်းချင်း ရှင်းလင်းချက်
- Trait က implement လုပ်တဲ့ type တွေ ပေးရမယ့် method signature တွေကို ကြေညာပါတယ်။
- Trait method မှာ mixin လို default body ရှိနိုင်ပါတယ်။ Type တွေက အလကား ရပြီး override လုပ်နိုင်ပါတယ်။
impl Describe for Dogက ထင်ရှားတဲ့ opt-in ပါ။Dogက field မပါတဲ့ unit struct ဖြစ်ပြီး type က behaviour ကိုပဲ သယ်တဲ့အခါ အသုံးဝင်ပါတယ်။.into()က&strကို signature လိုချင်တဲ့Stringအဖြစ် ပြောင်းပါတယ်။Robotက defaultdescribeကို override လုပ်ပါတယ်။&impl Describeဆိုတာ "Describeကို implement လုပ်တဲ့ ဘယ် type ရဲ့ reference မဆို" လို့ အဓိပ္ပာယ်ရပါတယ်။ ဒါက static dispatch ပါ — compiler ကDogအတွက်နဲ့Robotအတွက်announceရဲ့ သီးခြား copy တွေ ထုတ်လို့ call တွေက တိုက်ရိုက်ဖြစ်ပြီး inline လုပ်နိုင်ပါတယ်။- Trait bound ပါတဲ့ generic function ပါ —
Tက>နဲ့ နှိုင်းယှဉ်လို့ရ (PartialOrd) ပြီး ကူးလို့ရ (Copy) ရပါမယ်။ TS ရဲ့<T extends …>လိုပေမဲ့ bound တွေက စွမ်းရည် တွေကို စာရင်းပြုစုပြီး body က အဲဒါတွေကိုပဲ သုံးကြောင်း compiler က စစ်ပါတယ်။&[T]က slice ဖြစ်ပြီး array ဒါမှမဟုတ်Vecဘယ်ဟာရဲ့မဆို borrow လုပ်ထားတဲ့ မြင်ကွင်းပါ။ dyn Describeက dynamic dispatch ပါ — JS method call လို method ကို run နေချိန်မှာ table ထဲက ရှာတဲ့ trait object ပါ။ Size မတူတဲ့ value တွေ list တစ်ခုထဲ နေနိုင်အောင်Boxက value တစ်ခုစီကို heap ပေါ် တင်ပါတယ်။- Type parameter အများကြီး ပါပြီး တစ်ခုစီက
Display({}နဲ့ print ထုတ်လို့ရ) နဲ့ bound လုပ်ထားပါတယ်။ String နဲ့ integer နှစ်ခုလုံး ကိုက်ပါတယ်။ - Trait object တွေရဲ့
Vecက မတူတဲ့ concrete type တွေကို collection တစ်ခုထဲ သိမ်းပါတယ်၊Describeတွေရဲ့ TS array နဲ့ အနီးစပ်ဆုံးပါ။ Type annotation က compiler ကိုBox<Dog>နဲ့Box<Robot>တစ်ခုစီကိုBox<dyn Describe>အဖြစ် ပြောင်းဖို့ ပြောပါတယ်။
Collection တွေ၊ iterator တွေနဲ့ closure တွေ
သင်အကြိုက်ဆုံး array method တွေ အကုန်ဒီမှာ ရှိပါတယ်၊ ကွာခြားချက် နှစ်ခုနဲ့ပေါ့ — chain တစ်ခု စဖို့ .iter() ကို ခေါ်ရပြီး iterator တွေက lazy ပါ။ တစ်ခုခုက consume မလုပ်မချင်း (sum၊ collect၊ for loop) ဘာမှ မ run ပါဘူး။ ပြီးရင် compiler က chain တစ်ခုလုံးကို ကြားထဲမှာ ယာယီ array မရှိဘဲ loop တစ်ခုတည်းအဖြစ် ပေါင်းပေးပါတယ်။
const prices = [12, 5, 30, 8];const total = prices.filter((p) => p > 6).map((p) => p * 2).reduce((a, b) => a + b, 0);တစ်ကြောင်းချင်း ရှင်းလင်းချက်
- JS မှာ အဆင့်တိုင်းက array အသစ် ဆောက်ပါတယ် —
filterက တစ်ခု၊mapက နောက်တစ်ခု ဆောက်ပြီးမှreduceက လျှောက်ပါတယ်။
Tab က indent · ⌘/Ctrl + Enter က run · Esc က editor ထဲက ထွက်
တစ်ကြောင်းချင်း ရှင်းလင်းချက်
Vec<T>က ကြီးထွားနိုင်တဲ့ list ဖြစ်ပြီး Rust ရဲ့Arrayပါ။vec![…]က literal ကနေ တစ်ခု ဆောက်ပေးတဲ့ macro ပါ။pushလုပ်ဖို့mutဖြစ်ရပါမယ်။- TS version နဲ့ chain အတူတူပါ။
.iter()က vector ကို consume မလုပ်ဘဲ element တစ်ခုချင်းကို borrow လုပ်ပြီး.sum()က chain ကို မောင်းပါတယ်။totalပေါ်က type annotation ကsumကို ဘာထုတ်ရမလဲ ပြောပါတယ်။ - Closure က
&&i32(iterator ရဲ့ reference ကို ညွှန်တဲ့ reference) ကို လက်ခံပြီး pattern|&&p|က နှစ်ခုလုံးကို ခွာပါတယ်။ အဲဒီအစား|p| **p > 6ဒါမှမဟုတ်.copied()(မှတ်စု 5 လို) ကို မကြာခဏ တွေ့ရပါလိမ့်မယ်။ Iterator code ရဲ့ အရှုပ်ဆုံး အပိုင်းဖြစ်ပြီး မှားရင် compiler က ပြောပါတယ်။ collect()က iterator ကို collection တစ်ခုအဖြစ် စုပါတယ်။ အမျိုးအစား အများကြီး (Vec၊HashMap၊String…) ဆောက်နိုင်လို့ ဘယ်ဟာလဲဆိုတာ type annotation နဲ့ ပြောရပါတယ်။joinက JS လိုပဲ အလုပ်လုပ်ပါတယ်။.copied()က&i32item တွေကို ရိုးရိုးi32value တွေ အဖြစ် ပြောင်းပါတယ်။Vec<_>က element type ကို compiler ဖြည့်ခိုင်းတာပါ။ Closure က ပတ်ဝန်းကျင် scope ထဲကthresholdကို JS closure အတိုင်းပဲ သုံးပါတယ်။HashMapကMapပါ။ Key နဲ့ value တွေမှာ type ရှိပါတယ်။ အစဉ်လိုက် မဟုတ်ပါဘူး — iterate လုပ်တဲ့ အစဉ်ကို သတ်မှတ်မထားလို့ အားမကိုးပါနဲ့။- Entry API — key တစ်ခုရဲ့ value ကို ယူ၊ မရှိရင်
0ထည့်ပြီး နေရာမှာပဲ ပေါင်းပါတယ်။*ကor_insertပြန်ပေးတဲ့&mut u32ကို dereference လုပ်ပါတယ်။ JS ရဲ့map.set(k, (map.get(k) ?? 0) + 5)အစား lookup တစ်ခါတည်းပါ။ - တည်ငြိမ်တဲ့ output အတွက် key တွေကို စုပြီး sort လုပ်ပါ။
sortက နေရာမှာပဲ sort လုပ်ပါတယ်၊ ပြောင်းပြန် အစဉ်အတွက်sort_by(|a, b| b.cmp(a))ရှိပါတယ်။ - Key မရှိနိုင်လို့
getကOptionပြန်ပေးပါတယ်။stock["plums"]ဆိုရင်တော့ panic ဖြစ်ပါလိမ့်မယ်။ - ဒီစာကြောင်းမှာ ဘာမှ မ run သေးပါဘူး —
lengthsက လုပ်ရမယ့်အလုပ်ရဲ့ ဖော်ပြချက် သက်သက်ပါ။max()က consume လုပ်ပြီး ဗလာ iterator မှာ အမြင့်ဆုံး မရှိလို့Option<usize>ပြန်ပေးပါတယ်။
Module တွေ၊ crate တွေနဲ့ visibility
ES module တစ်ခုက file တစ်ခုပါ။ Rust module ကတော့ mod နဲ့ ကြေညာတဲ့ namespace ဖြစ်ပြီး inline ဒါမှမဟုတ် သူ့ကိုယ်ပိုင် file ထဲမှာ ရှိနိုင်ပါတယ်။ အရာအားလုံးက default အနေနဲ့ private ဖြစ်ပြီး pub က export လုပ်ပေးပါတယ်။
Tab က indent · ⌘/Ctrl + Enter က run · Esc က editor ထဲက ထွက်
တစ်ကြောင်းချင်း ရှင်းလင်းချက်
mod geometry { … }က module ကို inline ကြေညာပါတယ်။ တကယ့် project မှာmod geometry;လို့ ရေးပြီး Cargo ကsrc/geometry.rs(ဒါမှမဟုတ်src/geometry/mod.rs) ကို ဖတ်ပါတယ်။- Struct ပေါ်က
pubက type ကို module အပြင်ကနေ မြင်ရစေပြီး field တစ်ခုစီပေါ်ကpubက အဲဒီ field ကို ဖတ်/ရေးလို့ ရစေပါတယ်။pub xမပါရင် အပြင်က code ကPointကို ကိုင်ထားနိုင်ပေမဲ့Point { x, y }နဲ့ ဆောက်လို့ မရပါဘူး — invariant တွေ ထိန်းပုံက ဒါပါ — field တွေကို ဖုံးပြီး စစ်ဆေးပေးတဲ့newကို ပေးပါ။ pubမပါ —squareကgeometryနဲ့ သူ့ child တွေအတွက် private ပါ၊ JS module က export မလုပ်တဲ့ function လိုပေါ့။- Module တွေ အဆင့်ဆင့် ထပ်နိုင်ပါတယ်။
geometry::shapesက child module ပါ။ superက parent module ပါ၊ import path ထဲက../လိုပေါ့။crate::က crate ရဲ့ root ကနေ စတာဖြစ်ပြီး absolute path လိုပါ။useက path တစ်ခုကို scope ထဲ ယူလာပါတယ်၊import { Point } from "./geometry"လိုပေါ့။ တခြား crate တွေလည်း ဒီအတိုင်းပါ —cargo add randပြီးရင်use rand::Rng;လို့ ရေးပါတယ်။
Disk ပေါ်မှာ အဲဒီ crate ကိုပဲ file တွေခွဲထားရင် ဒီလိုပုံရပါတယ်။ index.ts မရှိပါဘူး — child တွေကို ထည့်ပေးတာက parent file ထဲက mod စာကြောင်းတွေပါ။
Cargo.tomlsrc/├── main.rs mod geometry; ← includes src/geometry.rs├── lib.rs (optional: the library half of the crate)├── geometry.rs pub mod shapes; ← includes src/geometry/shapes.rs└── geometry/ └── shapes.rstests/└── api.rs integration tests, compiled as a separate crateတစ်ကြောင်းချင်း ရှင်းလင်းချက်
main.rsက binary crate ရဲ့ root ဖြစ်ပြီးlib.rsက library crate ရဲ့ root ပါ။ ဘယ်modစာကြောင်းကမှ မဖော်ပြတဲ့ file ကို compile မလုပ်ပါဘူး — မသုံးတဲ့ file တစ်ခုမှ build ထဲ ခိုးမဝင်နိုင်ပါဘူး။- Package တစ်ခုက library နဲ့ binary ကို တွဲထားနိုင်ပါတယ်။ ဒါဆိုရင် binary က library ကို တခြား dependency တစ်ခုလိုပဲ
use your_crate_name::…နဲ့ သုံးပါတယ်။ tests/ထဲက file တွေက သုံးစွဲသူ တစ်ယောက်လိုပဲ သင့် crate ရဲ့ public API ကိုပဲ သုံးနိုင်ပါတယ်။ Unit test တွေကတော့ နောက်မှာ ပြမယ့်အတိုင်း code ဘေးမှာ နေပါတယ်။
Testing က ပါပြီးသား
Jest မလို၊ Vitest မလို၊ config မလိုပါဘူး — function တစ်ခုကို #[test] လို့ မှတ်လိုက်ရင် cargo test က run ပေးပါတယ်။ ဒီ block မှာ Run ကို နှိပ်ကြည့်ပါ — test တွေပါပြီး main မပါတဲ့ file ကို ဒီမှာလည်း cargo test အဖြစ် run ပါတယ်။
Tab က indent · ⌘/Ctrl + Enter က run · Esc က editor ထဲက ထွက်
တစ်ကြောင်းချင်း ရှင်းလင်းချက်
- Test လုပ်မယ့် function ပါ၊ တခြား module တွေ (test module အပါအဝင်) သုံးနိုင်အောင်
pubထားပါတယ်။ splitက ဘယ် character တွေက ခွဲတဲ့ နေရာလဲ ဆုံးဖြတ်တဲ့ closure ကို လက်ခံပါတယ်။splitက pattern အမျိုးအစား အများကြီး လက်ခံလို့ ဒီမှာc: charရဲ့ type ကို ရေးရပါတယ်။- "Turbofish"
::<Vec<_>>က variable ကို annotate မလုပ်ဘဲcollectရဲ့ target type ကို inline ပေးပါတယ်။ #[cfg(test)]က ဒီ module ကိုcargo testအတွက်ပဲ compile လုပ်လို့ test တွေနဲ့ test helper တွေက release binary ထဲ ဘယ်တော့မှ မရောက်ပါဘူး။- Test module က child ဖြစ်လို့
super::*က private function တွေအပါအဝင် parent ထဲက အရာအားလုံးကို import လုပ်ပါတယ်။ Rust မှာ private code ကို တိုက်ရိုက် unit-test လုပ်နိုင်ပါတယ်။ #[test]က test တစ်ခုကို မှတ်ပါတယ်။ Test တွေကို default အနေနဲ့ parallel thread တွေမှာ run လို့ တစ်ခုနဲ့တစ်ခု မမှီခိုရပါဘူး။assert_eq!က နှိုင်းယှဉ်ပြီး fail ဖြစ်ရင် ဘက်နှစ်ဖက်လုံးကို print ထုတ်ပါတယ်။assert!(condition)နဲ့assert_ne!လည်း ရှိပါတယ်။ မျှော်လင့်ထားတဲ့ string ကို ဖျက်ဆီးပြီး ပြန် Run ကြည့်ပါ၊ failure report ကို မြင်ရပါလိမ့်မယ်။#[should_panic]က body က panic ဖြစ်မှပဲ pass ဖြစ်ပါတယ် — မမှန်တဲ့ input ကို ငြင်းကြောင်း test လုပ်ပုံပါ။
Smart pointer တွေ — Box၊ Rc နဲ့ RefCell
တစ်ခါတလေ owner တစ်ယောက်တည်း မလုံလောက်ပါဘူး၊ ဒါမှမဟုတ် value တစ်ခုက heap ပေါ်မှာ နေရပါတယ်။ အဲဒီ case တွေအတွက် standard library မှာ smart pointer အနည်းငယ် ရှိပါတယ်။ တစ်ခုစီက heap memory အချို့ကို ပိုင်ပြီး drop ဖြစ်တဲ့အခါ free လုပ်တဲ့ ရိုးရိုး struct တစ်ခုပါ။
Tab က indent · ⌘/Ctrl + Enter က run · Esc က editor ထဲက ထွက်
တစ်ကြောင်းချင်း ရှင်းလင်းချက်
- Recursive type က indirection လိုပါတယ် —
Listတစ်ခုကListတစ်ခုလုံးကို တိုက်ရိုက် ထည့်ထားလို့ မရပါဘူး၊ ထည့်ရင် size က အဆုံးမရှိ ဖြစ်သွားမှာပါ။Box<List>က heap ပေါ် allocate လုပ်ထားတဲ့Listကို ညွှန်တဲ့ size သိပြီးသား pointer ပါ။Boxက အရိုးရှင်းဆုံး smart pointer ပါ — heap ပေါ်က တစ်ခုခုရဲ့ owner တစ်ယောက်တည်းပေါ့။ Boxက အလိုအလျောက် dereference ဖြစ်လို့rest(&Box<List>) ကို&Listလိုတဲ့နေရာမှာ ပေးလို့ ရပါတယ်။1 → 2 → endlist ကို ဆောက်တာပါ။listdrop ဖြစ်တဲ့အခါBoxတစ်ခုစီက သူ့ node ကို အလှည့်ကျ free လုပ်ပါတယ်။Rc("reference counted") က thread တစ်ခုတည်းထဲမှာ value တစ်ခုကို owner အများကြီး ရှိခွင့်ပြုပါတယ်။ JS object တွေ အလုပ်လုပ်ပုံနဲ့ အနီးစပ်ဆုံးပါ — နောက်ဆုံး owner ထွက်သွားမှ value ကို free လုပ်ပါတယ်။Rc::cloneက string ကို မကူးပါဘူး — count ကို တစ်တိုးပြီး တူညီတဲ့ value ကို ညွှန်တဲ့ pointer နောက်တစ်ခု ပြန်ပေးပါတယ်။ (x.clone()အစား)Rc::clone(&x)လို့ ရေးတာက ဖတ်သူတွေကို အဲဒါ ထင်ရှားစေပါတယ်။- အခု owner သုံးယောက်ပါ။ Shared ဖြစ်ပြီး mutable ဖြစ်တာက borrow rule တွေ တားမြစ်တာ အတိအကျ ဖြစ်လို့
Rcထဲက value တွေက read-only ပါ။ RefCellက borrow စစ်ဆေးမှုကို compile time ကနေ run time ဆီ ရွှေ့ပါတယ် — shared handle ကနေ အထဲက အကြောင်းအရာကို ပြင်ခွင့်ရပြီး mutable borrow နှစ်ခု ထပ်တာနဲ့ panic ဖြစ်ပါတယ်။Rc<RefCell<T>>က thread တစ်ခုထဲမှာ shared mutable state အတွက် ပုံမှန် ချက်နည်းပါ။borrow_mut()က statement အဆုံးမှာ ပြီးဆုံးတဲ့ ယာယီ mutable borrow တစ်ခု ပေးပါတယ်။borrow()က ယာယီ shared borrow ပါ။ ဒီ type တွေကို နည်းနည်းပဲ သုံးပါ — Rust code အများစုက ဘယ်ဟာမှ မလိုပါဘူး၊ နေရာတိုင်းမှာRc<RefCell<…>>ကို ကိုင်နေရတာက ownership နဲ့ ဆန့်ကျင်နေတဲ့ ဒီဇိုင်းရဲ့ လက္ခဏာ ဖြစ်တတ်ပါတယ်။
မကြောက်ရတဲ့ concurrency — thread၊ channel နဲ့ lock
JavaScript မှာ event loop တစ်ခုစီအတွက် thread တစ်ခုပဲ ရှိပြီး default အနေနဲ့ ဘာမှ မမျှသုံးတဲ့ Web Worker တွေ ပေးပါတယ်။ Rust က memory မျှသုံးနိုင်တဲ့ တကယ့် OS thread တွေ ပေးပြီး တူညီတဲ့ ownership rule တွေ က data race တွေကို compile time မှာ တားပါတယ်။ Marker trait နှစ်ခုက အလုပ်လုပ်ပါတယ် — Send (တခြား thread ဆီ ရွှေ့လို့ safe) နဲ့ Sync (thread တွေကြား မျှသုံးလို့ safe)။ ဥပမာ compiler က Rc ကို thread ကျော်ပြီး ပို့တာကို ငြင်းပါတယ်။
Tab က indent · ⌘/Ctrl + Enter က run · Esc က editor ထဲက ထွက်
တစ်ကြောင်းချင်း ရှင်းလင်းချက်
- Id တစ်ခုစီအတွက် thread တစ်ခုစီ စုစုပေါင်း သုံးခု စပြီး သူတို့ handle တွေကို သိမ်းထားပါတယ်။
collect()က အရေးကြီးပါတယ် — iterator တွေက lazy ဖြစ်လို့ မပါရင် thread တစ်ခုမှ နောက်မကျမချင်း မစပါဘူး။ thread::spawnက closure တစ်ခုကို OS thread အသစ်ပေါ်မှာ run ပါတယ်။ Thread က လက်ရှိ function ထက် ပိုကြာကြာ ရှင်နိုင်လို့ borrow လုပ်တာ safe မဖြစ်တဲ့အတွက်moveက closure ကို သူသုံးတဲ့ အရာ (id) ရဲ့ ownership ကို ယူစေပါတယ်။join()က promise ကို await လုပ်သလို thread ကို စောင့်ပြီး သူ့ result ကို ပြန်ပေးပါတယ်။ Thread က panic ဖြစ်ခဲ့နိုင်လို့Resultဖြစ်ပါတယ်။- Counter မျှသုံးဖို့ —
Mutexက value ကို တစ်ကြိမ်မှာ thread တစ်ခုပဲ ထိနိုင်အောင် စောင့်ပေးပြီးArc("atomic reference counted") က thread တိုင်းကို handle ပိုင်ခွင့်ပေးတဲ့ thread-safeRcပါ။ - Thread တစ်ခုစီက ရွှေ့ယူဖို့ ကိုယ်ပိုင်
Arcclone တစ်ခု ရပါတယ်။ Data ကိုယ်တိုင်ကို မကူးပါဘူး။ lock()က mutex ကို စောင့်ပြီး value ဆီ dereference ဖြစ်တဲ့ guard တစ်ခု ပြန်ပေးပါတယ်။ Guard drop ဖြစ်တဲ့အခါ (ဒီမှာ statement အဆုံး) lock ကို လွှတ်လို့ unlock လုပ်ဖို့ မေ့လို့ မရပါဘူး။Arc<Mutex<i32>>ကို thread တိုင်းက ဖမ်းယူတဲ့ ရိုးရိုးlet mut counter = 0နဲ့ အစားထိုးကြည့်ပါ — race ကြောင့် update တွေ ပျောက်အောင် ခွင့်မပြုဘဲ compiler က ငြင်းပါတယ်။- အမြဲ 8000 ပါ။ C မှာ synchronise မလုပ်ထားတဲ့ thread တွေနဲ့ဆိုရင် ဘယ်ဂဏန်းမဆို ထွက်နိုင်ပါတယ် — Rust မှာတော့ အဲဒီ version က compile မဖြစ်ပါဘူး။
- Go ရဲ့ channel လို channel တစ်ခုပါ —
txက ပို့ပြီးrxက လက်ခံပါတယ်။mpscဆိုတာ ပို့သူအများ၊ လက်ခံသူတစ်ယောက် လို့ အဓိပ္ပာယ်ရလို့ thread တစ်ခုစီကtxရဲ့ clone တစ်ခုစီ ရပါတယ်။ - ပို့သူ အားလုံး ပျောက်သွားမှ လက်ခံတဲ့ loop ပြီးဆုံးပါတယ်။ Worker thread တွေက ပြီးတဲ့အခါ သူတို့ clone တွေကို drop လုပ်ပြီး ဒီစာကြောင်းက မူရင်းကို drop လုပ်ပါတယ်။
rx.iter()က channel မပိတ်မချင်း message တစ်ခုချင်းအတွက် စောင့်ပါတယ်။ Thread တွေက ဘယ်အစဉ်နဲ့မဆို ပြီးနိုင်လို့ တည်တည်ငြိမ်ငြိမ် print ထုတ်ဖို့ message တွေကို sort လုပ်ပါတယ်။- Scoped thread တွေက
moveဒါမှမဟုတ်Arcမပါဘဲ local data (data) ကို borrow လုပ်နိုင်ပါတယ်၊thread::scopeက return မပြန်ခင် အားလုံးကို စောင့်လို့ သူတို့ borrow လုပ်တဲ့အရာထက် ပိုမကြာနိုင်လို့ပါ။
Tokio နဲ့ Async Rust
Thread တွေက CPU အလုပ်အတွက် ကောင်းပါတယ်။ Network connection ထောင်ချီအတွက်တော့ JS ကနေ ကောင်းကောင်း သိပြီးသား async ကို လိုချင်ပါတယ်။ Syntax က အတူတူနီးပါးဖြစ်ပြီး model မှာ ကွာခြားချက် နှစ်ခု ရှိပါတယ်။ Rust Future က lazy ပါ — await ဒါမှမဟုတ် spawn မလုပ်မချင်း ဘာမှ မလုပ်ပါဘူး၊ JS promise ကတော့ ဖန်တီးလိုက်တဲ့ အချိန်မှာပဲ စပါတယ်။ ပြီးတော့ built-in event loop မရှိပါဘူး — runtime crate တစ်ခု ထည့်ရပြီး အမြဲလိုလို Tokio ပါ။
const sleep = (ms: number) => new Promise((r) => setTimeout(r, ms));async function fetchUser(id: number) { await sleep(100 * id); return 'user ' + id;}const [a, b] = await Promise.all([fetchUser(1), fetchUser(2)]);တစ်ကြောင်းချင်း ရှင်းလင်းချက်
Promise.allက နှစ်ခုလုံးကို တစ်ပြိုင်နက် run ပြီး နှစ်ခုလုံးကို စောင့်ပါတယ် — စုစုပေါင်း 300 ms မဟုတ်ဘဲ 200 ms လောက်ပါ။
Tab က indent · ⌘/Ctrl + Enter က run · Esc က editor ထဲက ထွက်
တစ်ကြောင်းချင်း ရှင်းလင်းချက်
async fnကasync functionကPromise<string>ပြန်ပေးသလိုStringအစားFuture<Output = String>ကို ပြန်ပေးပါတယ်။.awaitက postfix keyword (expression နောက်မှာ ရေးတာ) ဖြစ်လို့ chain တွေက ဘယ်ကနေ ညာကို ဖတ်ရပါတယ် —client.get(url).send().await?.json().await?။ Worker thread တစ်ခုလုံးကို block လုပ်မယ့်std::thread::sleepကို ဘယ်တော့မှ မသုံးဘဲ Tokio ရဲ့sleepကို သုံးပါ။- ဒီ attribute macro က Tokio runtime ကို စပြီး
mainကို အဲဒီပေါ်မှာ run ပါတယ်။ Tokio က scheduler၊ timer၊ TCP နဲ့ file I/O၊ channel တွေကို ပေးပါတယ်။ - Async fn ကို ခေါ်တာက future ကို ဆောက်ရုံပဲ ဆောက်ပါတယ်။ ဒီမှာ ဘာမှ မဖြစ်ပါဘူး — timer မစ၊ drop လုပ်ရင် ရိုးရိုးလေး လွှင့်ပစ်ပါတယ်။ JS promise ဆိုရင် run နေပြီ ဖြစ်မှာပါ။
join!က future နှစ်ခုလုံးကို လက်ရှိ task ထဲမှာ တစ်ပြိုင်နက် poll လုပ်ပြီး နှစ်ခုလုံးကို စောင့်ပါတယ် —Promise.allပေါ့။ (ပထမ ပြီးတဲ့ တစ်ခုကို ယူတဲ့select!လည်း ရှိပါတယ် —Promise.raceပေါ့။)tokio::spawnက future တစ်ခုကို လွတ်လပ်တဲ့ task အဖြစ် runtime ဆီ လွှဲပေးပြီး တခြား worker thread ပေါ်မှာ run နိုင်ပါတယ်၊ promise တစ်ခုကို await မလုပ်သေးဘဲ လွှတ်လိုက်သလိုပေါ့။ Task တွေက စျေးပေါလို့ server တစ်ခုမှာ သိန်းချီ ရှိနိုင်ပါတယ်။JoinHandleကို await လုပ်ရင်Resultရပါတယ် — task က panic ဖြစ်ခဲ့ရင် ဒါမှမဟုတ် cancel ခံရရင်Errပါ။timeoutက ဘယ် future ကိုမဆို deadline နဲ့ ထုပ်ပြီး အချိန်မီ မပြီးရင်Err(Elapsed)ပြန်ပေးပါတယ်။ 500 ms ကြာမယ့် fetch ကို 50 ms မှာ drop (cancel) လုပ်ပါတယ်။ Rust မှာ future ကို drop လုပ်တာက cancel လုပ်ပုံပါ။- Run တစ်ခုလုံး 550 ms လောက် ကြာပါတယ်။ အစဉ်လိုက် run ရင် အဲဒီ await တွေက 900 ms ကျော် ကြာပါလိမ့်မယ်။
serde နဲ့ JSON
TypeScript မှာ JSON.parse လုပ်ပြီး Zod နဲ့ ပုံစံကို စစ်တာမျိုး လုပ်နိုင်ပါတယ်။ Rust မှာတော့ serde က parser ကို သင့် struct ကနေ derive လုပ်ပေးလို့ parse လုပ်ရင်းနဲ့ ပုံစံကို စစ်ပြီး type ပါတဲ့ value ဒါမှမဟုတ် တိကျတဲ့ error တစ်ခု ရပါတယ်။ Ecosystem ထဲမှာ အသုံးအများဆုံး crate တွေထဲက တစ်ခုပါ။
Tab က indent · ⌘/Ctrl + Enter က run · Esc က editor ထဲက ထွက်
တစ်ကြောင်းချင်း ရှင်းလင်းချက်
Serializeနဲ့Deserializeကို derive လုပ်တာက JSON code ကို compile time မှာ ထုတ်ပေးပါတယ်။ Run နေချိန်မှာ reflection မဖြစ်လို့ serde က အရမ်းမြန်ပါတယ်။- Rust field တွေက snake_case ဖြစ်ပြီး JSON က camelCase ဖြစ်တတ်ပါတယ် — ဒီ attribute က
display_nameကို"displayName"နဲ့ ချိတ်ပေးပါတယ်။ Optionfield က TS မှာemail?: string | nullလိုnullဒါမှမဟုတ် key မပါတာကို လက်ခံပါတယ်။#[serde(default)]က မပါတဲ့ key ကို type ရဲ့ default ဖြစ်တဲ့ ဗလာVecနဲ့ ဖြည့်ပါတယ်။mainကိုယ်တိုင်ကResultပြန်နိုင်လို့?က အထဲမှာ အလုပ်လုပ်ပါတယ်။Errဆိုရင် error ကို print ထုတ်ပြီး fail exit code နဲ့ program ပြီးဆုံးပါတယ်။- Raw string
r#"…"#က အထဲက quote တွေကို escape လုပ်စရာ မလိုလို့ JSON နဲ့ regex တွေအတွက် အဆင်ပြေပါတယ်။ Userထဲကို တိုက်ရိုက် parse လုပ်ပါတယ်။ Type annotation က ဘာအဖြစ် parse လုပ်ရမလဲ ရွေးပါတယ် — ဒါက Zod schema နဲ့ TS type ကို တစ်ခုတည်းမှာ ပေါင်းထားတာပါ။- Type မှားတာကို parse လုပ်ရင်းနဲ့ input ထဲက နေရာနဲ့တကွ ဖမ်းပါတယ်။
unwrap_err()က error ကို ထုတ်ယူပါတယ် (parse အောင်မြင်ခဲ့ရင် panic ဖြစ်ပါလိမ့်မယ်)။ - Serialise လုပ်တာက ပြောင်းပြန် လမ်းကြောင်းပါ။ Struct update syntax က tag တစ်ခုပါတဲ့
userရဲ့ ပြင်ထားတဲ့ copy တစ်ခု လုပ်ပါတယ်။ serde_json::Valueက TS ရဲ့unknownလို ပုံစံ မသိတဲ့ JSON အတွက်ပါ။ မရှိတဲ့ key ကို index လုပ်ရင် panic မဖြစ်ဘဲnullရပါတယ်။
unsafe အကြောင်း အကျဉ်းချုပ်
အခုထိ အားလုံးက safe Rust ပါ — dangling pointer နဲ့ data race မရှိကြောင်း compiler က သက်သေပြပါတယ်။ unsafe block ထဲမှာ အပိုအရာ ငါးခု လုပ်နိုင်ပြီး အဓိကက raw pointer တွေကို dereference လုပ်တာနဲ့ C function တွေကို ခေါ်တာပါ။ Compiler က ကျန်တာအားလုံးကို ဆက်စစ်ပါတယ် — unsafe ဆိုတာ "ဒီလုပ်ဆောင်ချက် အနည်းငယ် valid ဖြစ်ကြောင်း programmer ကျွန်တော် အာမခံပါတယ်" လို့ပဲ အဓိပ္ပာယ်ရပါတယ်။
Tab က indent · ⌘/Ctrl + Enter က run · Esc က editor ထဲက ထွက်
တစ်ကြောင်းချင်း ရှင်းလင်းချက်
- Raw pointer
*mut i32က C-style pointer ပါ။ ဖန်တီးတာက safe ပါ — compiler မစစ်နိုင်တာက အဲဒီကနေ ဖတ်တာ ဒါမှမဟုတ် ရေးတာပါ။ - Dereference လုပ်တာကို
unsafeထဲမှာ လုပ်ရပါမယ်။ Block က reviewer ကြည့်ရမယ့် နေရာကို အတိအကျ မှတ်ပေးပါတယ်။ get_uncheckedကv[2]လုပ်တဲ့ bounds check ကို ကျော်ပါတယ်။ အဆုံးကျော်တဲ့ index နဲ့ဆိုရင် undefined behaviour ဖြစ်လို့unsafeဖြစ်ရတာပါ။ ဒီလို နည်းလမ်းတွေ မသုံးခင် တိုင်းတာပါ — မလိုကြောင်း သက်သေပြနိုင်တဲ့ bounds check တွေကို optimiser က များသောအားဖြင့် ဖယ်ပေးပါတယ်။ Application code မှာunsafeကို မလိုသလောက်ပါပဲ။
Project အသေးလေး — စကားလုံး အကြိမ်ရေ report
ဒါက အရင်က အရာအများစုကို သုံးထားတဲ့ program တစ်ခုပါ — constant တစ်ခု၊ struct တစ်ခု၊ trait implementation တစ်ခု၊ iterator pipeline တစ်ခု၊ HashMap တစ်ခု၊ ကိုယ်ပိုင် အစဉ်နဲ့ sort လုပ်တာ၊ ပြီးတော့ Result။ စာသား ဒါမှမဟုတ် limit ကို ပြောင်းပြီး run ကြည့်ပါ။
Tab က indent · ⌘/Ctrl + Enter က run · Esc က editor ထဲက ထွက်
တစ်ကြောင်းချင်း ရှင်းလင်းချက်
conststring slice ပါ။ တကယ့် CLI မှာ ဒါက file (std::fs::read_to_string(path)?) ဒါမှမဟုတ် standard input ကနေ လာပါလိမ့်မယ်။- Result type — စုစုပေါင်းနဲ့ ထိပ်ဆုံး စကားလုံးတွေကို သူတို့ count တွေနဲ့
(String, usize)tuple တွေအဖြစ်ပါ။ Displayကို implement လုပ်တာက report ကိုtoString()ကို override လုပ်သလို{}နဲ့ ကိုယ်တိုင် print ထုတ်စေပါတယ်။writeln!က formatter ထဲကို စာကြောင်းတစ်ကြောင်း ရေးပြီး?က write error ရှိရင် လွှဲပေးပါတယ်။- Format spec တွေ —
{:>2}က column နှစ်ခုမှာ ညာဘက်ကပ်ပြီး{word:<6}က ခြောက်ခုမှာ ဘယ်ဘက်ကပ်လို့ bar တွေ တန်းညီပါတယ်။"#".repeat(n)က JS ရဲ့"#".repeat(n)လိုပဲ bar ကို ဆောက်ပါတယ်။*nက iterator ပေးတဲ့&usizeကို dereference လုပ်ပါတယ်။ - စကားလုံးကနေ count ဆီ ချိတ်ထားတဲ့ owned map တစ်ခု ပြန်ပေးပါတယ်။ Map ရဲ့ type တွေကို အောက်မှာ သုံးပုံကနေ infer လုပ်ပါတယ်။
- စာလုံးမဟုတ်တဲ့ ဘာပေါ်မှာမဆို ခွဲတာက space၊ full stop နဲ့ comma တွေကို တစ်ခါတည်း ကိုင်တွယ်ပါတယ်။
str::to_lowercaseက method ကို function အဖြစ် ပေးတာပါ၊.map(s => s.toLowerCase())လိုပေမဲ့ ပိုတိုပါတယ်။- ဗလာ input အတွက် error ပါ။ Program အသေးလေးမှာ ရိုးရိုး
Stringကို error type အဖြစ် သုံးလို့ ရပါတယ်။ into_iter()က map ကို consume လုပ်ပြီး သူ့ key နဲ့ value တွေကို vector ထဲ move လုပ်လို့ ဘာမှ မကူးပါဘူး။ ဒီနောက်countsကို သုံးလို့ မရတော့ပါဘူး။- Count နဲ့ အများဆုံးကနေ sort လုပ်ပြီး (
aမတိုင်ခင်b) တူနေရင်then_withနဲ့ အက္ခရာစဉ်အလိုက် ခွဲပါတယ်။cmpကOrdering(Less၊Equalဒါမှမဟုတ်Greater) ပြန်ပေးပြီး comparator ရဲ့ အနုတ်၊ သုည ဒါမှမဟုတ် အပေါင်း ဂဏန်းရဲ့ Rust version ပါ။ - ရလဒ်နှစ်မျိုးလုံးကို ကိုင်တွယ်ပါ။
eprintln!ကconsole.errorလို standard error ဆီ ရေးပါတယ်။
Browser ထဲက Rust — WebAssembly
ဒီနေရာက Rust နဲ့ သင့်ရှိပြီးသား stack ဆုံတဲ့ နေရာပါ။ Rust က WebAssembly ဆီ compile ဖြစ်ပြီး wasm-bindgen က JS glue code နဲ့ TypeScript type တွေကို ထုတ်ပေးလို့ TS app တစ်ခုက Rust function တွေကို npm module တစ်ခုလို ခေါ်နိုင်ပါတယ်။ Browser ထဲက အလုပ်လေးတွေ — image နဲ့ video processing၊ parser၊ compression၊ game၊ cryptography — အတွက် သင့်တော်ပါတယ်။
use wasm_bindgen::prelude::*; #[wasm_bindgen]pub fn slugify(title: &str) -> String { title.to_lowercase().split_whitespace().collect::<Vec<_>>().join("-")}တစ်ကြောင်းချင်း ရှင်းလင်းချက်
cargo add wasm-bindgenပြီးနောက် prelude က attribute နဲ့ JS interop type တွေကို ယူလာပါတယ်။Cargo.tomlထဲမှာ crate က[lib]အောက်မှာcrate-type = ["cdylib"]လည်း လိုပါတယ်။#[wasm_bindgen]က function ကို JavaScript ဆီ export လုပ်ပြီး JS နဲ့ Wasm memory ကြား string တွေ ကူးပေးတဲ့ code ကို ထုတ်ပေးပါတယ်။
rustup target add wasm32-unknown-unknowncargo build --release --target wasm32-unknown-unknownwasm-bindgen --target web --out-dir pkg target/wasm32-unknown-unknown/release/slug.wasmတစ်ကြောင်းချင်း ရှင်းလင်းချက်
- WebAssembly target ကို တစ်ခါ install လုပ်ပြီး crate ကို
.wasmfile အဖြစ် build လုပ်ပါတယ်။ wasm-bindgenCLI (cargo install wasm-bindgen-cli) ကpkg/slug.js၊pkg/slug.d.tsနဲ့.wasmfile ကို ရေးပေးပါတယ်။ Vite လို bundler တွေက တိုက်ရိုက် import လုပ်နိုင်ပါတယ်။
import init, { slugify } from './pkg/slug.js'; await init();console.log(slugify('Zero to Hero in Rust')); // "zero-to-hero-in-rust"တစ်ကြောင်းချင်း ရှင်းလင်းချက်
- ရိုးရိုး ES module import ပါ။ ထုတ်ပေးထားတဲ့
.d.tsfile ကslugifyကို(title: string) => stringtype ပေးလို့ editor က autocomplete လုပ်ပေးပါတယ်။ init()က.wasmfile ကို တစ်ခါ download လုပ်ပြီး instantiate လုပ်ပါတယ်။- ဒီကစပြီး ရိုးရိုး function call ပါပဲ။ Rust code က browser ရဲ့ sandbox ထဲမှာ native နီးပါး အမြန်နှုန်းနဲ့ run ပါတယ်။
Rust နဲ့ ဘာတွေ ဆောက်နိုင်လဲ
- Command-line tool တွေ။
clapက argument တွေကို parse လုပ်ပြီး ရလဒ်က မြန်တဲ့ binary တစ်ခုတည်းပါ။ ripgrep၊ bat၊ fd၊ eza၊ starship နဲ့ uv (Python package manager) အားလုံး Rust ပါ။ - Web backend နဲ့ API တွေ။ Axum (Tokio team က)၊ Actix Web နဲ့ Rocket ကို database အတွက် SQLx ဒါမှမဟုတ် Diesel နဲ့ တွဲသုံးပါတယ်။ Service တွေက memory နည်းနည်းပဲ သုံးပြီး latency အရမ်း တည်ငြိမ်ပါတယ်။
- JavaScript tooling။ ခေတ်သစ် JS toolchain ရဲ့ အများစုက အခု Rust ပါ — SWC (Next.js နောက်က compiler)၊ Turbopack (Next.js 16 ရဲ့ default bundler)၊ Rspack၊ Rolldown နဲ့ Oxc (Vite team ရဲ့ bundler နဲ့ parser)၊ Biome (formatter နဲ့ linter)၊ Lightning CSS နဲ့ Deno runtime။
- Browser၊ edge platform (Cloudflare Workers က JS ဘေးမှာ Wasm ကို run ပါတယ်) နဲ့ plugin system တွေအတွက် WebAssembly။
- Desktop နဲ့ mobile app တွေ။ Tauri က web front end (React၊ Svelte၊ …) ကို Rust back end နဲ့ ပေးပြီး app တွေက Electron ထက် အများကြီး သေးပါတယ်။
- Systems software — operating-system အစိတ်အပိုင်းတွေ (Linux kernel driver၊ Windows နဲ့ Android ရဲ့ အစိတ်အပိုင်းတွေ)၊ database၊ proxy (Cloudflare ရဲ့ Pingora)၊ virtual machine (AWS Firecracker)။
- Embedded နဲ့ IoT။
no_stdRust ကembassyasync framework နဲ့ chip တစ်ခုစီအတွက် HAL crate တွေကနေတစ်ဆင့် operating system မပါတဲ့ microcontroller တွေပေါ်မှာ run ပါတယ်။ - Game၊ graphics နဲ့ data။ Bevy engine၊ platform တိုင်းမှာ GPU အလုပ်အတွက်
wgpu၊ ပြီးတော့ pandas ထက် အများကြီး မြန်တဲ့ DataFrame library ဖြစ်တဲ့ Polars။
အားသာချက်နဲ့ အားနည်းချက်
Rust ထူးချွန်တဲ့ နေရာတွေ
- Garbage collector မပါတဲ့ memory safety။ Android vulnerability အားလုံးထဲမှာ memory-safety bug တွေ ပထမဆုံးအကြိမ် 20% အောက် ကျသွားကြောင်း၊ သူတို့ Rust code မှာ C နဲ့ C++ ထက် ဒီလို bug တစ်ထောင်ဆလောက် နည်းကြောင်း Google က ၂၀၂၅ နိုဝင်ဘာမှာ report လုပ်ခဲ့ပါတယ်။
- အမြန်နှုန်းနဲ့ ကြိုခန့်မှန်းနိုင်မှု။ Performance က C နဲ့ C++ နဲ့ တန်းတူဖြစ်ပြီး GC မရှိလို့ pause-time spike တွေ မရှိပါဘူး — Discord နဲ့ Figma က service တွေကို ပြောင်းရွှေ့ခဲ့တာ ဒါကြောင့် အတိအကျပါ။
- "Compile ဖြစ်ရင် များသောအားဖြင့် အလုပ်လုပ်တယ်။" Type system၊
Option၊Resultနဲ့ exhaustivematchက ဘာမှ မ run ခင် bug အမျိုးအစား တစ်ခုလုံးကို ဖမ်းလို့ codebase ကြီးကြီးကို refactor လုပ်ရတာ အများကြီး ကြောက်စရာ နည်းပါတယ်။ - မကြောက်ရတဲ့ concurrency။ Data race တွေက compile error တွေပါ။
- အလွန်ကောင်းတဲ့ tooling။ Cargo၊ rustfmt၊ Clippy၊ rust-analyzer နဲ့ built-in test နဲ့ doc (
cargo doc) တွေက install တိုင်းမှာ ပါပြီး project တိုင်းမှာ တစ်ပုံစံတည်း အလုပ်လုပ်ပါတယ်။ - သင်ပေးတဲ့ compiler message တွေ။ Error တွေက ဘာမှားလဲ ရှင်းပြပြီး များသောအားဖြင့် ပြင်နည်းကို အကြံပြုပါတယ်။
- နေရာတိုင်းလိုလို run ပါတယ် — server၊ Wasm ကနေတစ်ဆင့် browser၊ ဖုန်း၊ microcontroller၊ dependency မပါတဲ့ binary တစ်ခုတည်းအဖြစ်ပါ။
နာကျင်ရတဲ့ နေရာတွေ
- မတ်စောက်တဲ့ learning curve။ Ownership၊ borrowing နဲ့ lifetime တွေ သဘာဝကျလာဖို့ ရက်သတ္တပတ်တွေ ကြာပါတယ်။ "Borrow checker နဲ့ တိုက်ရတဲ့" ကာလတစ်ခုကို မျှော်လင့်ထားပါ။ အဲဒါ ပြီးဆုံးသွားပြီး ချန်ထားခဲ့တဲ့ အလေ့အထတွေက ဘယ်ဘာသာစကားမှာမဆို ပိုကောင်းတဲ့ programmer ဖြစ်စေပါတယ်။
- Compile time နှေးတယ်။ Monomorphisation နဲ့ LLVM optimisation က အချိန်ကုန်ပါတယ်။ Project ကြီးတစ်ခုရဲ့ clean build က မိနစ်တွေ ကြာနိုင်ပါတယ်။
cargo check၊ incremental build နဲ့ crate တွေ ခွဲတာက ကူညီပါတယ်။ - Prototype လုပ်ဖို့ နှေးတယ်။ Script မြန်မြန် ဒါမှမဟုတ် စမ်းသပ်မှုတစ်ခုအတွက် TypeScript ဒါမှမဟုတ် Python က ပိုမြန်မြန် ရောက်စေပါတယ်။ Rust က error နဲ့ edge case တိုင်းကို အစကတည်းက ကိုင်တွယ်ခိုင်းပါတယ်။
- Async မှာ ကြမ်းတမ်းတဲ့ အစွန်းတွေ ရှိတယ်။ Pinning၊ future တွေပေါ်က
Sendbound တွေနဲ့ runtime ရွေးတာက JS ရဲ့ async ထက် ခက်ပြီး အဲဒီမှာ error message တွေက အဖော်ရွေမှု အနည်းဆုံးပါ။ - တချို့ နယ်ပယ်တွေမှာ ecosystem က ငယ်သေးတယ်။ Web back end နဲ့ CLI တွေက ရင့်ကျက်ပြီး GUI toolkit၊ တချို့ enterprise SDK နဲ့ ML training ကတော့ JS၊ Python ဒါမှမဟုတ် Java ထက် ပါးပါတယ်။
- ဝန်ထမ်းခန့်ခြင်းနဲ့ team ရဲ့ အရှိန်ယူချိန်။ Rust ကို ကောင်းကောင်း သိတဲ့ developer က TS ဒါမှမဟုတ် Go ထက် နည်းလို့ အသုံးပြုမယ့် team က လေ့ကျင့်ဖို့ အချိန်လိုပါတယ်။
Rust၊ Go နဲ့ TypeScript ကို ဘေးချင်းယှဉ်
ဒီသုံးခုက ကုမ္ပဏီတစ်ခုတည်းမှာ အတူတူ ရှိတတ်လို့ တစ်ခုစီက ဘာမှာ အကောင်းဆုံးလဲ သိထားရင် အထောက်အကူ ဖြစ်ပါတယ်—
- Memory။ TS (V8) နဲ့ Go က garbage-collected ဖြစ်ပြီး Rust က ownership သုံးကာ GC မရှိပါဘူး။ Go ရဲ့ GC pause တွေက တိုပေမဲ့ load များတဲ့အခါ tail latency မှာ ပေါ်လာနေဆဲပါ။
- Concurrency။ TS — async/await ပါတဲ့ event loop တစ်ခု။ Go — ဘာသာစကားထဲ ပါပြီးသား goroutine နဲ့ channel၊ သုံးခုထဲမှာ အရိုးရှင်းဆုံး model။ Rust — thread နဲ့ async၊ data race တွေကို compile time မှာ ပယ်ထားပါတယ်။
- Type system။ TS — structural ဖြစ်ပြီး အားကောင်းပေမဲ့ ဖယ်ပစ်ထားပြီး ဒီဇိုင်းအရ unsound ဖြစ်ပါတယ် (
any)။ Go — ရိုးရှင်း၊ nominal၊ 1.18 ကစပြီး generic ပါပြီး feature နည်းနည်းပဲ တမင်ထားပါတယ်။ Rust — ကြွယ်ဝပြီး (data ပါတဲ့ enum၊ trait၊ generic၊ lifetime) sound ဖြစ်ပါတယ်။ - Error တွေ။ TS — exception။ Go —
if err != nilreturn value။ Rust —?ပါတဲ့Result၊ Go လို ထင်ရှားပေမဲ့ အများကြီး ပိုတိုပါတယ်။ - Learning curve။ Go က သင်ယူဖို့ အလွယ်ဆုံး (weekend တစ်ခု)၊ JS သိရင် TS က ဒုတိယ၊ Rust က အခက်ဆုံး (ရက်သတ္တပတ်တွေကနေ လတွေအထိ)။
- အကောင်းဆုံး နယ်ပယ်။ TS — UI၊ full-stack web app နဲ့ browser နဲ့ server ကြား code မျှသုံးတာ။ Go — network service၊ CLI နဲ့ cloud infrastructure ကို team သေးသေးနဲ့ မြန်မြန် (Docker၊ Kubernetes နဲ့ Terraform ကို Go နဲ့ ရေးထားပါတယ်)။ Rust — performance အရေးကြီးတဲ့ လမ်းကြောင်း၊ systems software၊ tooling၊ embedded နဲ့ Wasm။
ကုမ္ပဏီကြီးတွေက TypeScript/JavaScript၊ Go နဲ့ Rust ကို ဘယ်လို တွဲသုံးလဲ
ကုမ္ပဏီကြီးတွေက ဘာသာစကား တစ်ခုတည်းကို ရွေးလေ့ မရှိပါဘူး။ အဖြစ်များတဲ့ ပုံစံက product မျက်နှာစာအတွက် TypeScript၊ network service နဲ့ infrastructure အများစုအတွက် Go၊ ပြီးတော့ latency၊ memory ဒါမှမဟုတ် safety အရေးအကြီးဆုံး နေရာမှာ Rust ပါ။ မှတ်တမ်းကောင်းကောင်း ရှိတဲ့ ဥပမာ တချို့—
- Discord။ Desktop နဲ့ web client တွေက JavaScript (React၊ desktop မှာ Electron) ပါ။ User တိုင်း ဘာဖတ်ပြီးပြီလဲ မှတ်ထားတဲ့ Read States service ကို Go နဲ့ ရေးခဲ့ပြီး Go ရဲ့ garbage collector က နှစ်မိနစ်လောက်တစ်ခါ latency spike တွေ ဖြစ်စေလို့ Rust ဆီ ပြောင်းခဲ့ပါတယ်။ Optimise မလုပ်ရသေးတဲ့ ပထမဆုံး Rust version ကတောင် tune လုပ်ထားတဲ့ Go version ထက် ပိုကောင်းခဲ့ပါတယ်။
- Figma။ Editor UI က TypeScript ဖြစ်ပြီး rendering engine က WebAssembly ဆီ compile လုပ်ထားတဲ့ C++ ပါ။ Document တွေကို sync လုပ်တဲ့ multiplayer server က TypeScript နဲ့ စခဲ့ပြီး garbage-collection pause တွေကြောင့် တစ်စိတ်တစ်ပိုင်း မလိုက်နိုင်တော့တဲ့အခါ Rust နဲ့ ပြန်ရေးခဲ့ပါတယ်။ Go နဲ့ ရေးထားတဲ့ ကျယ်ကျယ်ပြန့်ပြန့် သုံးနေတဲ့ esbuild bundler ကို Figma ရဲ့ ပူးတွဲတည်ထောင်သူ Evan Wallace က ဖန်တီးခဲ့တာပါ။
- Microsoft။ TypeScript 7 (၂၀၂၆) က JavaScript အခြေခံ compiler ကို Go နဲ့ ရေးထားတဲ့ native compiler နဲ့ အစားထိုးခဲ့ပြီး project ကြီးတွေမှာ ဆယ်ဆလောက် ပိုမြန်ပါတယ်။ Go ရဲ့ ဖွဲ့စည်းပုံနဲ့ garbage collection က ရှိပြီးသား code ကို စာကြောင်းအလိုက်နီးပါး port လုပ်နိုင်စေလို့ team က Go ကို ရွေးခဲ့တာပါ။ Microsoft က Windows kernel ထဲမှာ Rust ကိုလည်း ထည့်သွင်းထားပြီး Azure ရဲ့ CTO က GC မပါတဲ့ project အသစ်တွေကို C/C++ အစား Rust သုံးဖို့ တိုက်တွန်းခဲ့ပါတယ်။
- Cloudflare။ Developer တွေက Worker တွေကို JavaScript နဲ့ TypeScript (ဒါမှမဟုတ် Wasm ဆီ compile လုပ်ထားတဲ့ Rust) နဲ့ ရေးပါတယ်။ သူ့ HTTP proxy Pingora ကို NGINX ကို အစားထိုးဖို့ Rust နဲ့ ရေးခဲ့ပြီး တစ်ရက်ကို request တစ်ထရီလီယံကျော် ကိုင်တွယ်ကာ အခု open source ဖြစ်ပါပြီ။ Internal service အများကြီးကို Go နဲ့ ရေးထားပါတယ်။
- Vercel။ Next.js က TypeScript ဖြစ်ပေမဲ့ အောက်က compiler က Rust ပါ — SWC က code ကို transform လုပ်ပြီး Rust ပဲ ဖြစ်တဲ့ Turbopack က Next.js 16 မှာ default bundler ဖြစ်လာခဲ့ပါတယ်။
- Amazon Web Services။ AWS Lambda function တိုင်းနဲ့ Fargate task တိုင်းကို run တဲ့ micro-VM Firecracker ကို Rust နဲ့ ရေးထားပါတယ်။ Infrastructure-as-code toolkit ဖြစ်တဲ့ AWS CDK ကို TypeScript နဲ့ ရေးထားပြီး AWS က Go၊ JavaScript နဲ့ Rust အတွက် တရားဝင် SDK တွေ ထိန်းသိမ်းပါတယ်။
- Google။ Go ကို ဖန်တီးခဲ့ပြီး သူ့ cloud တစ်ခုလုံးမှာ သုံးကာ Angular (TypeScript) က သူ့ front-end framework ပါ။ Android ရဲ့ low-level code အသစ်တွေကို Rust နဲ့ ရေးပြီး အပေါ်က security ရလဒ်တွေ ရခဲ့ပါတယ်။
- Dropbox။ Desktop sync engine Nucleus ကို Rust နဲ့ ပြန်ရေးခဲ့ပြီး engine ကို test လုပ်ရ ပိုလွယ်အောင် type system ကို သုံးကာ "မမှန်တဲ့ state တွေကို ဒီဇိုင်းကနေ ဖယ်ရှား" ခဲ့ပါတယ်။
- Linux kernel။ ၂၀၂၅ ဒီဇင်ဘာ Maintainers Summit မှာ Rust ကို experimental မဟုတ်တော့ကြောင်း ကြေညာခဲ့ပါတယ် — အခု C နဲ့အတူ kernel ရဲ့ အမြဲတမ်း အစိတ်အပိုင်း ဖြစ်ပါပြီ။
JS/TS developer တစ်ယောက်အတွက် သင်ခန်းစာ — ရွေးစရာ မလိုပါဘူး။ TypeScript ထူးချွန်တဲ့နေရာမှာ ဆက်ထားပြီး ရိုးရှင်းမြန်ဆန်တဲ့ service လိုရင် Go ကို၊ တကယ့် performance၊ memory ဒါမှမဟုတ် safety နံရံကို တိုက်မိရင် Rust ကို ကိုင်ပါ။ သင့် toolchain အများကြီးကို အခု Go နဲ့ Rust နဲ့ ရေးထားလို့ သူတို့ကို သင်ယူတာက နေ့တိုင်း သုံးနေတဲ့ tool တွေကို ဖတ်ပြီး ပါဝင်ကူညီနိုင်စေပါတယ်။
သုညကနေ Hero ဆီ သွားမယ့် လမ်းပြမြေပုံ
- ပထမ ရက်သတ္တပတ် — အခြေခံတွေ။ rustup နဲ့ install လုပ်၊ editor မှာ rust-analyzer ထည့်ပြီး ဒီစာမျက်နှာက ဥပမာတိုင်းကို run ကြည့်ကာ ပျက်သွားတဲ့အထိ တစ်ခုချင်း ပြင်ကြည့်ပါ။ The Rust Programming Language ("the book"၊ doc.rust-lang.org/book မှာ အခမဲ့) ရဲ့ အခန်း ၁–၆ ကို ဖတ်ပါ။
- ဒုတိယ ရက်သတ္တပတ် — တကယ့် ownership။ Rustlings လေ့ကျင့်ခန်းတွေ လုပ်ပါ။ Borrow checker က ညည်းတဲ့အခါ ဘာမှ မပြောင်းခင် error message တစ်ခုလုံးနဲ့ အကြံပြုထားတဲ့ ပြင်နည်းကို ဖတ်ပါ။
- တတိယ ရက်သတ္တပတ် — CLI tool တစ်ခု။ သင်သုံးနေတဲ့ Node script အသေးလေးတစ်ခုကို
clap၊serdeနဲ့anyhowသုံးပြီး Rust နဲ့ ပြန်ရေးပါ။ Clippy ကို warning မရှိဘဲ pass ဖြစ်အောင် လုပ်ပါ။ - စတုတ္ထ ရက်သတ္တပတ် — trait၊ generic နဲ့ testing။ Book ကို အခန်း ၁၁ အထိ ပြီးအောင် ဖတ်ပြီး သင့် CLI အတွက် unit test တွေ ရေးပါ။
- ဒုတိယလ — async နဲ့ web။ Tokio tutorial ကို လုပ်ပြီး Axum နဲ့ SQLx သုံးကာ JSON API အသေးလေးတစ်ခု ဆောက်ပြီး TypeScript front end ကနေ ခေါ်ပါ။
- တတိယလ — ပိုနက်နက် သွားပါ။ Concurrency (book အခန်း ၁၆)၊ smart pointer နဲ့ lifetime ကို အသေးစိတ်၊ ပြီးတော့ Jon Gjengset ရဲ့ Rust for Rustaceans။ တစ်ခုခုကို WebAssembly ဆီ compile လုပ်ပြီး React app ကနေ သုံးပါ။
- ဆက်သွားပါ။ ခြောက်ပတ်တစ်ခါ release note တွေကို ဖတ်၊ This Week in Rust ကို follow လုပ်ပြီး SWC၊ Biome ဒါမှမဟုတ် ripgrep လို သင်သုံးနေပြီးသား tool တွေရဲ့ source ကို ဖတ်ပါ။ အဲဒီထဲက တစ်ခုမှာ issue အသေးလေးတစ်ခု ပြင်ပေးပါ။
Rust က JavaScript က ကိုက်မခံရမချင်း လျစ်လျူရှုခွင့်ပေးထားတဲ့ ownership အကြောင်းကို အစကတည်းက စဉ်းစားခိုင်းပါတယ်။ Compiler က တင်းကျပ်ပေမဲ့ သင့်ဘက်မှာ ရှိပါတယ် — သူ ဖြတ်သန်းခွင့်ပေးပြီးတာနဲ့ ညဉ့်နက်ပိုင်း bug အမျိုးအစား တစ်ခုလုံး ပျောက်သွားပါတယ်။

