Rust Backend против Go Backend в веб-разработке

Перевод | Автор оригинала: Lane Wagner

Rust и Go - два самых больших успеха отрасли, когда дело касается разработки современных языков программирования. Оба языка конкурируют с точки зрения серверной веб-разработки, и это жесткая конкуренция. Golang и Rust - новые языки, у них растут сообщества, они быстрые и эффективные. Когда дело доходит до микросервисных архитектур, фреймворков и приложений, Rust и Go широко используются в серверной части.

Сходства

Расширенные стандартные библиотеки

Rust и Go имеют много общих черт, особенно когда дело касается веб-разработки. У них обоих есть богатые стандартные библиотеки, хотя Go, как правило, имеет более ориентированные на Интернет протоколы, такие как HTTP, поддерживаемые из коробки.

Открытый источник

Оба языка имеют открытый исходный код, что означает, что ни одна компания в ближайшее время не будет выдергивать исходный код из-под нас, хотя стоит отметить, что Google движет разработкой Go, а Rust - это скорее проект сообщества. Есть плюсы и минусы поддержки компании. Go извлекает выгоду из ресурсов и PR крупной технологической компании, в то время как Rust никогда не подвергнется риску навязывания специфических для Google повесток дня проекту.

Относительно новые языки

Go и Rust - это новые языки, а это значит, что они не обладают тем багажом устаревшей и обратной совместимости, который вы можете найти с такими языками, как Java и Javascript.

Релиз Rust v1: 15 мая 2015 г.

Релиз Go v1: 28 марта 2012 г.

Производительность

Показатели производительности обычно ставят Rust прямо перед Go, но ненамного. Компилятор Rust и дизайн языка позволяют разработчикам легко использовать оптимизацию, которая обеспечивает скорость, сравнимую с подобными C. С другой стороны, Go жертвует небольшой скоростью ради простоты и элегантного синтаксиса.

Rust против Go по скорости

Управление памятью

Оба языка претендуют на звание «безопасных для памяти», но используют разные подходы к его достижению. Rust обнаруживает ошибки памяти во время компиляции, в то время как Go использует сборщик мусора во время выполнения.

Rust использует стратегию владения во время компиляции за счет абстракций с нулевой стоимостью. Если программа на Rust небезопасна для памяти, она не скомпилируется. Для всех, кто часто сталкивается с ошибками памяти в языках C, это признано удивительной особенностью. Вместо умной оптимизации компилятора компилятор Go добавляет к завершенному исполняемому файлу небольшую среду выполнения, которая управляет выделением и освобождением памяти.

Хотя у обоих подходов есть свои плюсы и минусы, в целом оптимизация компилятора Rust приводит к более производительным программам. В качестве альтернативы код приложения Go более чистый, поскольку управление памятью полностью обрабатывается средой выполнения.

Rust против Go по памяти

Скорость разработки

Бывают случаи, когда во время разработки веб-приложения скорость разработки важнее скорости приложения. Python - отличный пример, это один из самых медленных языков, но у него самый чистый синтаксис. Хотя Go, как правило, быстрее и использует меньше памяти, чем такие языки, как Java, C#, JavaScript, Python и Ruby, он дает компромисс в производительности, чтобы обеспечить быструю и простую разработку.

Также стоит отметить, что Go и Rust становятся все популярнее. Если вы хотите узнать об этом больше, прочтите соответствующую статью «Rust vs Go - что более популярно?».

Сравнение кода

Теперь, когда мы концептуально сравнили и противопоставили Rust и Go, давайте посмотрим, как будет выглядеть простой HTTP-сервер на обоих языках.

Пример внутреннего HTTP-сервера на Rust

// example taken from https://gist.github.com/mjohnsullivan/e5182707caf0a9dbdf2d

use std::net::{TcpStream, TcpListener};
use std::io::{Read, Write};
use std::thread;


fn handle_read(mut stream: &TcpStream) {
    let mut buf = [0u8 ;4096];
    match stream.read(&mut buf) {
        Ok(_) => {
            let req_str = String::from_utf8_lossy(&buf);
            println!("{}", req_str);
            },
        Err(e) => println!("Unable to read stream: {}", e),
    }
}

fn handle_write(mut stream: TcpStream) {
    let response = b"HTTP/1.1 200 OK\r\nContent-Type: text/html; charset=UTF-8\r\n\r\n<html><body>Hello world</body></html>\r\n";
    match stream.write(response) {
        Ok(_) => println!("Response sent"),
        Err(e) => println!("Failed sending response: {}", e),
    }
}

fn handle_client(stream: TcpStream) {
    handle_read(&stream);
    handle_write(stream);
}

fn main() {
    let listener = TcpListener::bind("127.0.0.1:8080").unwrap();
    println!("Listening for connections on port {}", 8080);

    for stream in listener.incoming() {
        match stream {
            Ok(stream) => {
                thread::spawn(|| {
                    handle_client(stream)
                });
            }
            Err(e) => {
                println!("Unable to connect: {}", e);
            }
        }
    }
}

Кодовый язык: Rust (Rust)

Как вы заметили в приведенном выше коде, мы можем развернуть довольно простой HTTP-сервер без чего-либо, кроме стандартной библиотеки, хотя мы должны реализовать некоторые из протоколов HTTP самостоятельно. Обратите внимание, что мы включаем ответ HTTP / 1.1 200 OK непосредственно в поток данных, которым мы отвечаем. Это не значит, что нет других крэйтов, которые бы облегчили нашу жизнь, но Rust из коробки заставляет нас делать большую часть тяжелой работы самостоятельно.

Если вы посмотрите на стандартное создание HTTP, вы увидите, что это намеренный выбор не стандартизировать реализацию.

Примечательно, что в этом крэйте вы не найдете реализации отправки запросов или раскрутки сервера. Предполагается, что этот крэйт является «стандартной библиотекой» для HTTP-клиентов и серверов, не требуя какой-либо конкретной реализации. Обратите внимание, что этот крэйт все еще находится на ранней стадии своего жизненного цикла, поэтому библиотеки поддержки, которые интегрируются с крэйтом http, находятся в стадии разработки! Следите за обновлениями, и в будущем мы обязательно будем выделять здесь крэйти.

http - Rust

Пример внутреннего HTTP-сервера Golang

package main

import (
	"encoding/json"
	"net/http"
)

func hello(w http.ResponseWriter, req *http.Request) {
	type response struct {
		Message string `json:"message"`
	}
	resp := response{
		Message: "hello world",
	}
	respBytes, _ := json.Marshal(resp)
	w.Header().Set("Content-Type", "application/json")
	w.WriteHeader(200)
	w.Write(respBytes)
}

func main() {
	http.HandleFunc("/hello", hello)
	http.ListenAndServe(":8080", nil)
}

Язык кода: Go (go)

С другой стороны, с Go чрезвычайно легко развернуть рабочий веб-сервер, мы даже получаем поддержку JSON, заголовков и кодов состояния прямо из стандартной библиотеки.

Параллелизм

Параллелизм необходим в серверных приложениях. Некоторые языки работают в однопоточных средах, что означает, что они используют некоторые хитрые приемы для имитации параллелизма без достижения истинного параллелизма. И Rust, и Go имеют элегантные решения для создания полностью параллельных приложений, но Go снова берет пирог с точки зрения простоты, с ключевым словом go, являющимся единственным синтаксисом, необходимым для запуска нового потока:

go someFunc()

Язык кода: Go (go)

Победитель

Как обычно, явного победителя нет, результаты более тонкие.

Rust ценится…

Go предпочитают ...

Отличительная трэйта успешного программиста, даже самоучки, - это способность выбирать хорошие инструменты для конкретного проекта. Это не означает, что вам нужно быть экспертом во всех существующих языках, базах данных, технологиях и фреймворках, но это означает, что вам нужно знать их достаточно, чтобы принимать обоснованные решения. Способность принимать такие решения выделит вас среди других и даст вам больше возможностей для развития карьеры программиста.