Some applications have to earn every megabyte. They run alongside a customer’s existing software, process a continuous stream of data, or live on hardware where adding more memory isn’t an option. In those products, how efficiently the code runs can decide whether the business works.
That’s where Rust becomes particularly interesting to me.
In my article on C# and .NET, I explained why they’re my usual starting point for business applications. Rust gives me another set of strengths: native machine code, precise control over memory, and a compiler that checks how data is used before the application ever runs. When those properties matter to your product, Rust can be the preferable choice.
I’ve spent years working with it. I was one of the first five people on the Vector team and a core contributor for more than four years, building its GraphQL backend API and tools for understanding a running instance. After Datadog acquired Timber, I went on to lead fifteen engineers on Observability Pipelines. That experience connects the low-level engineering to the product decisions and operational demands around it.
Here’s what I value about Rust, with examples from an imaginary service that processes and routes events. These are individual pieces to explain the decisions, rather than a complete production service.
A small executable can be a product feature
If you’re distributing an agent that customers install on their own servers, deployment is part of the customer experience. Every runtime they need to install, library version they need to match and extra megabyte they need to download adds work to that experience.
Rust compiles ahead of time into native machine code. The destination machine doesn’t need a Rust toolchain, interpreter or separately installed managed runtime. With the right target and dependencies, I can also produce a statically linked executable that includes the libraries it needs. On Linux, a musl target is a common route to that. The linking configuration depends on the target, so I check the finished executable’s dependencies as part of packaging it for customers.
Size is something I can deliberately optimise. For a small command-line tool, this is a useful starting point in Cargo.toml, the project’s build configuration:
[profile.release]
opt-level = "s"
lto = "thin"
strip = "symbols"
Then build it with:
cargo build --release
These Cargo settings ask for size-oriented optimisation, optimisation across compiled modules, and removal of symbol information from the distributed binary. I measure the result with the actual dependencies included. A throughput-heavy service may benefit from a different optimisation profile; Rust gives me control over that choice.
For your business, a compact executable can mean easier installation, smaller deployment images and less storage consumed across a fleet of machines. We build for each supported operating system and processor, then give customers an artifact we can test, version and replace predictably.
Getting close to the CPU without giving up readable code
The attraction of Rust is how much control I get over the work the machine performs. I can choose data structures that sit together in memory, reuse buffers and inspect data without copying it into another object first.
Consider a simple event line such as orders:payment_received. This function returns the part before the colon:
fn event_source(line: &str) -> Option<&str> {
line.split_once(':').map(|(source, _)| source)
}
The returned &str is a borrowed view into the original text. This function doesn’t allocate a new string or copy those characters. Option makes the missing-colon case explicit: we either get Some(source) or None.
That looks like a small convenience until it’s in a path that runs for every event. Avoiding an allocation and a copy there reduces work at exactly the point where volume multiplies it. Rust lets me express that relationship directly, and the compiler ensures the borrowed text cannot outlive the data it points into.
Higher-level features fit this approach too. Iterators and generic code can compile down to efficient specialised operations; the Rust book demonstrates why iterator-based code needn’t be slower than a hand-written loop. Traits let me describe shared behaviour, and generics let the compiler resolve concrete implementations without requiring a runtime lookup for every call.
If a measured bottleneck warrants it, Rust also exposes processor-specific instructions, including SIMD operations that process several values at once. I can go from readable application code down to the CPU features that a particular workload benefits from. The commercial question is where that work improves throughput, response time or running cost enough to justify doing it.
The compiler tracks who can use the memory
Rust’s type checking extends beyond whether a value is a string or a number. Its ownership system tracks who owns a value and when responsibility for it moves. The compiler rejects uses that would break those rules.
Here’s a deliberately invalid example:
fn main() {
let payload = String::from("orders:payment_received");
let queued_payload = payload;
println!("{queued_payload}");
println!("{payload}"); // Error: payload has been moved.
}
The second assignment transfers ownership of the string. It doesn’t make another copy of its contents. Using payload afterwards is a compile error, so we can’t accidentally treat both variables as independent owners of the same allocation.
If I only need to inspect the string, I borrow it instead:
fn inspect(payload: &str) {
println!("Received {} bytes", payload.len());
}
fn main() {
let payload = String::from("orders:payment_received");
inspect(&payload);
println!("Still available: {payload}");
}
The borrowing rules govern access too: ordinary references allow shared reading or exclusive mutable access to the same data. Lifetimes describe how long references remain valid, often inferred without extra annotations. These checks prevent dangling references and conflicting access while allowing the program to work directly with existing memory.
Rust manages this without a tracing garbage collector periodically searching for unused objects. Owned resources are normally cleaned up when their owner is dropped. That gives me a way to control resource use in a long-running service without adding garbage-collection pauses to its workload.
The guarantee applies to safe Rust built on sound dependencies. Operations involving raw pointers or foreign code can require unsafe Rust, where the developer must uphold additional safety requirements. I want those boundaries small, documented and carefully reviewed. That makes the parts requiring manual memory-safety reasoning visible in the codebase.
Types that describe what can actually happen
I mentioned Rust’s enums in the C# article because they’re one of my favourite ways to describe a business process. An enum can represent several distinct outcomes, each carrying the information it needs.
For our event service, a delivery attempt might succeed, need a retry or be rejected:
enum DeliveryOutcome {
Delivered { receipt_id: String },
RetryAfter { seconds: u64 },
Rejected { reason: String },
}
fn describe(outcome: DeliveryOutcome) -> String {
match outcome {
DeliveryOutcome::Delivered { receipt_id } => {
format!("Delivered: {receipt_id}")
}
DeliveryOutcome::RetryAfter { seconds } => {
format!("Retry in {seconds} seconds")
}
DeliveryOutcome::Rejected { reason } => {
format!("Rejected: {reason}")
}
}
}
A delivered outcome carries a receipt; a rejection carries a reason. The data follows the meaning of the outcome. We don’t need a string status alongside unrelated optional fields that can contradict it.
Rust requires a match to cover every possibility. Remove the retry branch and this code won’t compile. Add another outcome later and the compiler identifies the matches that need attention, provided we haven’t used a catch-all branch to accept everything else.
Option<T> expresses a value that may be absent. Result<T, E> expresses success or an error. Together, they make decisions visible in function signatures: the next developer can see what might be missing or fail before reading the implementation. I like that particularly in systems that have to keep processing when a destination is temporarily unavailable.
Macros do useful work before the program starts
Rust’s compile-time ergonomics are a large part of why I enjoy it. Macros generate code during compilation, and that generated code goes through the compiler’s checks. Libraries can provide conveniences that feel built into the language.
For example, Serde can generate the code to serialise and deserialise an event from its type definition:
use serde::{Deserialize, Serialize};
#[derive(Debug, Serialize, Deserialize)]
struct Event {
source: String,
message: String,
attempt: u32,
}
fn decode_event(json: &str) -> Result<Event, serde_json::Error> {
serde_json::from_str(json)
}
This needs serde with its derive feature and serde_json. The derive integration creates the implementations from the fields I’ve declared. Changing the structure changes the generated serialisation code with it. If a field’s type doesn’t support the required operation, the build tells me.
The JSON itself is decoded and checked when it arrives. Result gives me an explicit error path for malformed input or incompatible fields, ready for the service’s validation and rejection rules. The combination saves repetitive conversion code while keeping both the data shape and the failure path visible.
SQLx checks SQL against the database schema
EF Core was a big part of my case for C#. In Rust, SQLx provides a different convenience I value: I can write SQL directly and have its query macros check it during the build.
Suppose our service stores delivery destinations in PostgreSQL:
CREATE TABLE destinations (
id BIGINT PRIMARY KEY,
organisation_id BIGINT NOT NULL,
endpoint TEXT NOT NULL,
last_error TEXT
);
Here’s a function using SQLx 0.8.6 with its postgres, macros and runtime-tokio features enabled:
use sqlx::PgPool;
#[derive(Debug)]
struct Destination {
id: i64,
endpoint: String,
last_error: Option<String>,
}
async fn load_destinations(
pool: &PgPool,
organisation_id: i64,
) -> Result<Vec<Destination>, sqlx::Error> {
sqlx::query_as!(
Destination,
r#"
SELECT id, endpoint, last_error
FROM destinations
WHERE organisation_id = $1
ORDER BY id
LIMIT 100
"#,
organisation_id
)
.fetch_all(pool)
.await
}
The query_as! macro checks the query’s columns against the Rust structure. PostgreSQL’s BIGINT becomes i64, TEXT becomes String, and the nullable last_error becomes Option<String>. The organisation ID is bound as a parameter. A caller would obtain it from the authenticated account context, and this example returns only that organisation’s first hundred destinations.
With the schema available during compilation, a misspelled column or incompatible result field can fail the build. I get direct SQL, editor completion on the returned Rust values and an early check that the two agree. That is particularly useful when a schema change affects queries spread through an application.
Make the schema part of the build
The macros need either a development database with the relevant schema, supplied through DATABASE_URL, or previously generated query metadata. The SQLx CLI can save that metadata for builds without a database connection:
cargo sqlx prepare
SQLX_OFFLINE=true cargo check
I generate it against a database with our migrations applied, keep the .sqlx metadata with the code, and use cargo sqlx prepare --check against the migrated schema in CI to detect stale metadata. SQLx also provides commands to create migration files and apply them; I write and review the SQL changes they contain.
Those checks establish agreement with the schema used for validation. Deployment still needs to apply the matching migrations, and integration tests cover permissions, constraints and actual query behaviour. Keeping that process together turns a database change into something we can assess before it reaches customers.
Efficient services need deliberate limits
Vector collects, transforms and routes observability data. It’s a good illustration of why efficiency matters: the software helping you understand a server should leave resources available for the application that server exists to run.
For a client project, the equivalent might be a log collector, an import worker or a service that validates and forwards events. It has a focused job, a clear input and output, and enough repeated work for careful memory use to pay off.
Rust’s Send and Sync traits help the compiler check which values can move between threads or be shared across them. For asynchronous services, Tokio supplies tools such as bounded channels: queues with a capacity, so producers can wait when consumers fall behind.
That limit is an architectural decision. If a destination slows down, we need to decide whether to wait, reject new work or persist it for later. I also set limits on event size and concurrent work. Memory safety and operational reliability address different problems; together, they help us build a service that behaves predictably under pressure.
My work on Vector’s instance observability is relevant here. Building an efficient service includes making it possible to see what it is doing. For your product, I want measurements of throughput, queue depth, errors and resource use so we can diagnose a bottleneck and test whether a change improves it.
From embedded devices to complete products
Rust is particularly well suited to software with a resource constraint or a need to interact closely with its environment. That includes parsers, protocol handlers, networking tools, storage components and small services deployed across many machines.
It also reaches environments without a conventional operating system. Rust’s no_std support lets code use the core library without depending on the full standard library. On a supported microcontroller, that can mean firmware using fixed buffers and explicitly controlled allocation. For a connected device business, fitting the software to the hardware’s memory and processing budget is part of designing the product itself.
Rust can support the broader application too. I built Uplevel end to end with Rust, PostgreSQL and TypeScript, bringing property listings, campaigns and contacts into one product. My Rust experience spans both the systems underneath a product and the workflows customers use.
We can also choose Rust for one component. A C# application might manage accounts, billing and the dashboard while a Rust process handles a demanding stream of data. I would introduce that boundary when the component has a clear job and the operational benefit earns the additional deployment. The choice belongs to the application we’re building and the team that will maintain it.
Why experienced Rust developers are hard to hire
Rust asks developers to reason about ownership, lifetimes, concurrency and the cost of their abstractions. Those concepts influence the design of the program. Becoming effective means learning how to structure the work so those constraints help you.
That makes hiring for substantial Rust work demanding. You need someone who understands the language and can apply it to production problems: choosing dependencies, diagnosing performance, handling failure and leaving a codebase another person can maintain. A working example on a résumé doesn’t establish that depth.
The 2025 State of Rust survey, published in March 2026, reports continued hiring interest among participating organisations alongside persistent concerns about complexity and slow compilation. For planning a team, I treat Rust as a specialist capability whose learning time and build workflow deserve a place in the budget.
An experienced Rust developer can recognise when an ownership problem calls for a simpler design, when copying a small value is a sensible tradeoff and when shared state needs a different approach. That judgment helps preserve the performance and maintainability you chose Rust to get.
Having that experience in your CTO connects the implementation to the commercial decision. I can assess whether Rust belongs in the product, build the component, review its behaviour under load and help a future team understand why it works that way. My experience at Vector and Datadog gives me a practical basis for those decisions, alongside the founder’s responsibility for what the work costs and what it achieves.
The foundations matter whichever language we choose
I hold a Bachelor of Science degree in Computer Science, which I passed with honours, from Goldsmiths, University of London. That education gives me a deep appreciation for how computers work at a core, foundational level, and it pairs naturally with Rust. Memory, algorithms, data structures and concurrency become concrete choices in the code I’m writing.
That understanding is useful when we choose C#, TypeScript or another language too. A framework can manage memory for us, but I still need to understand why an application retains too much of it. An asynchronous API can make concurrent work convenient, but I still need to know what happens when we start more work than the database can handle. The underlying machine continues to set the limits.
When I assess a slow feature, I want to distinguish time spent calculating from time spent waiting, identify unnecessary data movement and understand how the work grows with the number of customers. That helps us put effort into the cause: a better query, a different data structure, a bounded queue or a carefully chosen Rust component.
Through Blueprint Ventures, I bring that foundation together with more than twenty-five years of building software, running businesses and leading engineers. You work directly with me on both the technical direction and the implementation, with decisions explained in terms of your product and budget.
If you’re building something where performance, deployment or hardware constraints matter, tell me about your project. Share who it’s for, what it needs to do and where you want the business to go. We can work out where Rust would help and build around the requirements that matter.