326 private links
Some pitfalls and cases where JSON has a default behavior.
Date, BigInt, circular dependencies, undefined property are not preserved and leads to different behaviors.
Serialization executes code and toJSON().
Take care of prototype pollutions (for example a __proto__ property).
There are also optimization with Message Pack that provides a compact ninary representation and CBOR.
Full of great explanations about securing (hardening) Rust code.
- treat panic behavior as part of your API. Decide explicitly whether panics should unwind or abort, and avoid uncontrolled panics.
- enable stricter Clippy lints (such as indexing and arithmetic checks) when panic freedom is important.
- use a panic hook to shutdown gracefully, set reports, cleanup, collect diagnostics and
- sanitize panic (and logging) messages while logging.
veilis a good crate for that. - avoid unbounded recursion. Prefer iterative algorithms or use depth limits.
- releases builds behave differently. They should be accordingly tested.
- audit dependencies with cargo-audit and cargo-deny
- secure allocation (for defense-in-depth when using unsafe code or FFI) with
mimallocor similar. Measure performance before enabling it globally. - use minimal runtime images if needed (rust distroless). It reduces the attack surface.
- use multi-stage builds with
cargo-chef,--lockedand proper.dockerignore - avoid alpine musl lib c as it introduces subtle runtime differences; at least be aware of them.
- use Linux
landlockto restrict filesystem access even after compromission - never run as root unless absolutely necessary. Drop as much Linux capabilities as possible.
- use Miri to detect undefined behavior, invalid pointer usage and unsafe-ode bugs.
- handle SIGTERM/SIGINT properly. Stop accepting new work, finish inflight requests, flush buffers and then exit cleanly
- protect external dependencies (database, API, cache) with circuit breakers so repeated failures don't cascade
- put explicit limits on everything: upload size, request bodies, queues, timeouts, thread counts,
- expose two health endpoints: a liveness probe and a readiness probe. The liveness probe checks if the process is alive at all, while the readiness probe checks if the process is healthy enough to handle traffic.
- Use fuzzing (
cargo-fuzz,honggfuzz), coverage (cargo-llvm-cov), unsafe detection (cargo-geiger), and memory-analysis tools alongside Rust's compile-time guarantees.
The entire element such as a card is interactive as a button for visual users. The button or link remains correct for the rest of the users.
One of the recommendation is to use a 3-day cooldown before using new versions. Crazy.
- Stop allocating useless strings: to_string(), .clone(), HashMap borrow the string
- Optimize
hashmaplookups withhashmap.entry().or_insert(0) += 1instead of 3 lookups: in an if condition, insert operation and update. - Use as much core as possible: rewrite loops as iterators and then use rayon with
par_lines. remove()in array is a costly O(n) operation. If order is not important, preferswap_remove.
- Rust doesn't prevent TOCTOU (Time-of-Check to Time-of-Use) race conditions (file path resolution → use file handlers directly)
- Panics are denial-of-service vulnerabilities when handling untrusted input.
- In case of a well established tool, compatibility is a security feature.
- Resolve external information before crossing trust boundaries.
- The interactions with the operating system is a security boundary in Rust. The developer has to be careful.
The post goes in-depth for many cases.
The standard is available at https://www.itu.int/rec/T-REC-X.680/
It's used in many applications such as SNMP, CMIP, LSAP. Ididn't encounter a use case yet.
As stated in the video, the ASN.1 GNU library is under-documented and hard to use without examples. The idea, concept and design is well done though.
Recreating a button from scratch is hard. Here is why.
Semantic HTML tags improves the UX, so let's use the UX of HTML
<div> is chaotic neutral and many HTML tags fall back to a generic HTML tag.
<section>, <header>, <footer> and <div> containing texts are exposed as <p>
How to do a migration properly without downtime? Avoid a transaction on all rows.
Method:
- add new column as nullable
- new writes use the new column and read from the old-and new columns
- Deploy
- Fix the old data in a batch. Pause briefly between each batch and run another one once the database load is correct.
The short version: one file, one process, and the heaviest page in the app served 315 million requests a day on a M1-laptop. For almost anything you are building, SQLite in WAL mode is enough, and the Postgres container you spun up out of habit was never needed.
The SQLite setup:
const db = new Database('chirp.db');
db.pragma('journal_mode = WAL'); // readers and the writer stop blocking each other
db.pragma('synchronous = NORMAL'); // fsync at checkpoints, not on every commit
db.pragma('busy_timeout = 5000'); // wait for the write lock instead of throwing
db.pragma('foreign_keys = ON'); // off by default, which surprises people
db.pragma('cache_size = -64000'); // 64MB of page cache
Under WAL, across every scenario, zero SQLITE_BUSY errors. Not "few." Zero.
Limits of SQLite:
- Reads stop scaling once anything writes.
- There is only one writer.
- One machine: the recovery time is always the time needed to restore a file.
Reach for Postgres when you have many writers contending on the same rows, when you need read replicas or automatic failover, when you need a real analytics engine over hundreds of millions of rows, or when your team genuinely needs the extension ecosystem. Those are real reasons."
Advantages of SQLite:
- Backups are a file copy. From the shell:
sqlite3 chirp.db "VACUUM INTO 'backup-$(date +%F).db'" - Local development is one file.
- Resetting the database is
rm - Tests get a real database each
- Deploys are a binary and a file
- There is nothing to operate
To optimize SQLite:
- focus on single-core speed
- buy enough RAM to hold the database: wait for GBs
- local NVMe. Never put SQLite on network storage.
- Pay for a dedicated core if you care about p99.
- Use
stricttables - Only INT, INTEGER, REAL, TEXT, BLOB, and ANY are allowed, and ANY is there when you actually want a key-value column. Store your JSON in a TEXT column and use the JSON functions on it.
- Prepare every query on startup. Making it on every request is a way to make SQLite look slow
The hardest part is user acquisition either way. Don't loose time on operations.
The reflex to start with a database server is a habit, not an engineering decision. It made sense when SQLite did lock the whole file on every write. That stopped being true a long time ago, and the tooling caught up: WAL for concurrency, STRICT for type safety, Litestream for replication.
But after programming in Rust for 10 years, I think that your coding style has the biggest impact on how your Rust code will look and feel.
People often say Rust’s syntax is ugly, but I’d argue the syntax is the least interesting thing about Rust. The semantics (the bits and pieces the language provides to express your ideas and how those bits combine to build interesting things) are much more important.
Parsing a .env can be clunky in basic algorithm, but elegant in Rust.
Tips:
read_to_string()instead of using a path, opening a file, creating a vector, adding the content to the vector to make a string- Use type inference:
let mut cfg = HashMap::new() - Lean into the typesystem:
.lines()to split strings safely and iterates over the linessplit_once()to get each key-value pair for each lines
- leverage error handling: use an enum to list all possible errors with important values and
thiserrorcan handle the error message
fn parse_config_file(path: &str) -> Result<HashMap<String, String>, ParseError> {
let content = read_to_string(path)?;
let mut config = HashMap::new();
for line in content.lines() {
match KeyValue::try_from(line) {
Ok(kv) => { config.insert(kv.key, kv.value); },
Err(ParseError::InvalidLine(_)) => continue, // Skip invalid lines
Err(e) => return Err(e), // Fail on any other error
}
}
Ok(config)
}
and why this code structure offers more extensibility!
Rust’s beauty is in its semantics and the core mechanics it provides: ownership, borrowing, pattern matching, traits, and so on. If you merely look at its (admittedly foreign) syntax, you overlook the real elegance of the language.
If there is anything that makes Rust “ugly”, it isn’t its syntax but the fact that it doesn’t hide the complexity underneath. Rust values explicitness and you have to deal with the harsh reality that computing is messy. Turns out our assumptions about a program’s execution are often wrong and our mental models are flawed.
Fortunately, we can encapsulate a lot of the complexity behind ergonomic abstractions; it just takes some effort! So don’t worry: once you start to confront your bad habits and look around for better abstractions, Rust stops being ugly.
That’s also the reason NaN !== NaN. If NaN behaved like a number and had a value equal to itself, well, you could accidentally do math with it: NaN / NaN would result in 1, and that would mean that a calculation containing a NaN result could ultimately result in an incorrect number rather than an easily-spotted “hey, something went wrong in here” NaN flag.
How to check the value can be used for a calculation?
typeof theValue === "number": to me too, it feels clunky th compare strings in order to use a numberNumber.isNaN()does exactly what it means: it checks for theNaN- but the global function
isNaN()returns true "“if I tried to make you into a number, would that work, or would you end up being NaN?"
And here is the problem: the encryption performs 2 passes over the data: first to encrypt, then to compute the authentication tag.
As we've seen before, it's bad because you will pay huge penalties when loading / unloading your data to / from memory to / from SIMD registers multiple times, even if you AES-CTR and GHash implementation are optimized to the mooooon.