Go vs Rust: Hello world HTTP server performance comparison

Mayank C
Tech Tonic

--

THIS IS AN OUTDATED ARTICLE. AN UPDATED BENCHMARK HAS BEEN PUBLISHED HERE.

In this article, I’m going to compare Go’s native HTTP server with Rust’s Hyper HTTP server. Based on my research, I’ve found that Hyper is the most popular server choice on the Rust side. If there are better and popular alternatives, please let me know.

The comparison will be fair because both are compiled languages that produce a machine code. Let’s run the tests and check the results.

Before moving ahead, other comparisons are here:

Test setup

The tests are executed on MacBook Pro M1 with 16G of RAM.

The software versions are:

  • Go v1.20.2
  • Rust v1.68.2

The hello world HTTP server code in both cases is as follows:

Go

package main

import (
"io"
"net/http"
)

func main() {
http.HandleFunc("/", helloWorld)
http.ListenAndServe(":3000", nil)
}

func helloWorld(w http.ResponseWriter, r *http.Request) {
io.WriteString(w, "Hello world!")
}

Rust

use std::convert::Infallible;
use std::net::SocketAddr;

use bytes::Bytes;
use http_body_util::Full;
use hyper::server::conn::http1;
use hyper::service::service_fn;
use hyper::{Request, Response};
use tokio::net::TcpListener;

async fn hello(_: Request<hyper::body::Incoming>) -> Result<Response<Full<Bytes>>, Infallible> {
let response = Response::builder()
.header("Content-type", "text/plain")
.body(Full::new(Bytes::from("Hello World!")))
.unwrap();
Ok(response)
}

#[tokio::main]
pub async fn main() -> Result<(), Box<dyn std::error::Error + Send + Sync>> {
let addr: SocketAddr = ([127, 0, 0, 1], 3000).into();
let listener = TcpListener::bind(addr).await?;
loop {
let (stream, _) = listener.accept().await?;
tokio::task::spawn(async move {
if let Err(err) = http1::Builder::new()
.serve_connection(stream, service_fn(hello)).await {
println!("Error serving connection: {:?}", err);
}
});
}
}

Cargo.toml

[package]
name = "hello_world"
version = "0.1.0"
edition = "2021"

[dependencies]
bytes = "1"
hyper = { version = "1.0.0-rc.3", features = ["full"] }
tokio = { version = "1", features = ["full"] }
http-body-util = "0.1.0-rc.2"

Rust code has been built in release mode:

> cargo build --release
...
Compiling hyper v1.0.0-rc.3
Compiling hello_world v0.1.0 (/Users/mayankc/Work/source/perfComparisons/rust)
Finished release [optimized] target(s) in 19.88s

Execution

Each test is executed for 5M (5 million) requests.

The tests are executed for 10, 100, and 300 concurrent connections.

The load test is carried out using Bombardier HTTP testing tool.

The following are the tables showing results for each concurrency level:

Compared to comparisons with Bun and Deno, the competition was fair this time because both Go and Rust are compiled languages. They produce a machine code that runs directly. However, in terms of performance, Rust beats Go on all aspects.

WINNER: Rust

--

--