HTTP server in Rust vs Go:
same endpoint, both languages, full code.
TechEmpower Round 23 - plaintext req/s
| Framework | Language | Req/s | vs Go std |
|---|---|---|---|
| Actix-web 4 | Rust | 7.1M | +73% |
| Hyper 1.x | Rust | 6.8M | +66% |
| Axum 0.7 | Rust | 6.2M | +51% |
| Go Fiber v2 | Go | 5.8M | +41% |
| Go net/http | Go | 4.1M | baseline |
| Go Echo v4 | Go | 3.9M | -5% |
Source: techempower.com/benchmarks · Round 23 · Plain text, single query
Minimal HTTP server with routing
RUST / Axum
use axum::{
routing::get,
Router,
extract::Path,
};
#[tokio::main]
async fn main() {
let app = Router::new()
.route("/", get(|| async { "Hello, World!" }))
.route("/hello/:name", get(greet));
let listener = tokio::net::TcpListener::bind(
"0.0.0.0:3000"
).await.unwrap();
axum::serve(listener, app)
.await
.unwrap();
}
async fn greet(Path(name): Path<String>) -> String {
format!("Hello, {name}!")
}GO / net/http
package main
import (
"fmt"
"net/http"
"strings"
)
func main() {
mux := http.NewServeMux()
mux.HandleFunc("GET /", func(
w http.ResponseWriter,
r *http.Request,
) {
fmt.Fprintln(w, "Hello, World!")
})
mux.HandleFunc("GET /hello/{name}", greet)
http.ListenAndServe(":3000", mux)
}
func greet(w http.ResponseWriter, r *http.Request) {
name := strings.TrimPrefix(r.URL.Path, "/hello/")
fmt.Fprintf(w, "Hello, %s!", name)
}Go 1.22+ (Feb 2024): net/http's ServeMux now supports HTTP method routing (GET /path) and path parameters ({name}) in the pattern directly. Before 1.22, you needed a third-party router like gorilla/mux or chi. This eliminates the main reason teams reached for frameworks. Rust's Axumuses tower-service middleware, which makes adding auth, logging, and rate-limiting composable and type-safe, but adds cognitive overhead vs Go's handler function signature.