Skip to main content

Compare Rust and Golang Programming

· 11 min read
forfd8960
Author

In this blog, I will share the programming difference between Rust and Golang, Include:

  • How they use different syntax to declare data type.
  • How rust and golang define the common behavior(interface vs trait).
  • How rust and golang struct to implement the common behavior.
  • What's the difference between rust and golang about Error Handling.
  • How they define complex data type(struct).
  • How they define enum.
  • How they Obtaining and using third-party libraries.
  • What's the difference between rust and golang when write unit test.
  • What's the difference between rust and golang about async programming.

The Entry Point difference

package main
func main() {
fmt.Println("hello, world!")
}
fn main() {
println!("Hello, World");
}

Primitive Data Type

  • Rust
// Signed integers
let a: i8 = 127; // 8-bit
let b: i16 = 32767; // 16-bit
let c: i32 = 0; // 32-bit (default integer type)
let d: i64 = 0; // 64-bit
let e: i128 = 0; // 128-bit
let f: isize = 0; // pointer-sized

// Unsigned integers
let g: u8 = 255; // 8-bit
let h: u16 = 65535; // 16-bit
let i: u32 = 0; // 32-bit
let j: u64 = 0; // 64-bit
let k: u128 = 0; // 128-bit
let l: usize = 0; // pointer-sized
  • Golang
// Signed integers
var a int8 = 127 // 8-bit
var b int16 = 32767 // 16-bit
var c int32 = 0 // 32-bit
var d int64 = 0 // 64-bit
var e int = 0 // platform dependent (32 or 64 bit)

// Unsigned integers
var f uint8 = 255 // 8-bit
var g uint16 = 0 // 16-bit
var h uint32 = 0 // 32-bit
var i uint64 = 0 // 64-bit
var j uint = 0 // platform dependent

Function

  • Rust use fn to declare an function.

  • Golang use func to start a function.

  • Rust use pub fn to declare an function can be access from module.

  • Golang use func UpperCaseName to allow public access.

pub fn is_even(num: i64) -> bool {
num % 2 == 0
}
func isEven(num int64) bool {
return num % 2 == 0
}
  • Allow Public access for function
pub fn allow_access() -> String {
"Allow".to_string()
}
func AllowAccess() string {
return "Allow"
}
  • Receive multiple argsuments
pub fn merge(elements: Vec<String>) -> String {
elements.join(",")
}
import "strings"

func Merge(elements ...string) string {
return strings.Join(elements, ",")
}

How to use struct

#[derive(Debug, Clone)]
pub struct Person {
pub name: String,
pub age: u8,
pub location: Location,
}

#[derive(Debug, Clone)]
pub struct Location {
pub country: String,
pub city: String,
}
package structure

type Person struct {
Name string
Age uint8
Location *Location
}

type Location struct {
Country string
City string
}

Implment method on struct

#[derive(Debug, Clone, PartialEq)]
pub struct Location {
pub country: String,
pub city: String,
}

impl Person {
pub fn new(name: String, age: u8, loc: Location) -> Self {
Self {
name,
age,
location: loc,
}
}

pub fn come_from_same_place(&self, other: Person) -> bool {
self.location.eq(&other.location)
}
}
  • Golang interface
func NewPerson(name string, age uint8, loc Location) *Person {
return &Person{
Name: name,
Age: age,
Location: &loc,
}
}

func (p *Person) isSameLocation(other *Person) bool {
return p.Location.Country == other.Location.Country && p.Location.City == other.Location.City
}

Enum

pub enum Status {
Pending,
Scheduled,
Running,
Idle,
Stopped,
}
package enum

type Status int

const (
Pending Status = iota
Scheduled
Running
Idle
Stopped
)

interface VS trait

  • Rust Trait
pub trait AppRunner {
fn run_app(&self) -> Status;
}

pub struct Mac {}

impl AppRunner for Mac {
fn run_app(&self) -> Status {
println!("Mac runing app");
Status::Pending
}
}

pub struct IPhone {}

impl AppRunner for IPhone {
fn run_app(&self) -> Status {
println!("IPhone runing app");
Status::Scheduled
}
}

pub struct Android {}

impl AppRunner for Android {
fn run_app(&self) -> Status {
println!("Android runing app");
Status::Running
}
}

pub fn run_apps(runners: Vec<Box<dyn AppRunner>>) {
for runner in runners {
println!("{:?}", runner.run_app());
}
}

Run in Main

use basic_of_rs::common_behavior::{run_apps, Android, AppRunner, IPhone, Mac};

fn main() {
println!("Hello, world!");

let systems: Vec<Box<dyn AppRunner>> =
vec![Box::new(Mac {}), Box::new(IPhone {}), Box::new(Android {})];

/*
Hello, world!
Mac runing app
Pending
IPhone runing app
Scheduled
Android runing app
Running
*/
run_apps(systems);
}

  • Golang interface
package commonbehave

import "fmt"

type Status int

const (
Pending Status = iota
Scheduled
Running
Idle
Stopped
)

type AppRunner interface {
RunApp() Status
}

type MacSystem struct{}
type IPhone struct{}
type Android struct{}

func (ms *MacSystem) RunApp() Status {
fmt.Println("Mac running App")
return Pending
}

func (ms *IPhone) RunApp() Status {
fmt.Println("IPhone running App")
return Scheduled
}

func (ms *Android) RunApp() Status {
fmt.Println("Android running App")
return Running
}

func RunApps(runners ...AppRunner) {
for _, runner := range runners {
fmt.Printf("run result: %v\n", runner.RunApp())
}
}
  • run apps
package commonbehave

import "testing"

func TestRunApps(t *testing.T) {
RunApps(&MacSystem{}, &Android{}, &IPhone{})
}

/*
Mac running App
run result: 0
Android running App
run result: 2
IPhone running App
run result: 1
*/

Error handling

Rust can use ? to propagate the error to upper level function

use thiserror::Error;

#[derive(Debug, Error)]
pub enum Error {
#[error("empty elements")]
EmptyElements,
#[error("invalid num")]
InvalidNum,
}

pub fn sum_nums(nums: &[i32]) -> Result<i32, Error> {
if nums.len() == 0 {
return Err(Error::EmptyElements);
}

let mut total = 0;
let mut idx = 0 as usize;
loop {
if idx >= nums.len() {
break;
}

let j = idx + 1;

let mut next = 0 as i32;
if j < nums.len() {
next = nums[j];
}

let sum = sum2(nums[idx], next)?;
total += sum;
idx = j + 1;
}

Ok(total)
}

fn sum2(a: i32, b: i32) -> Result<i32, Error> {
if b < 0 {
return Err(Error::InvalidNum);
}

Ok(a + b)
}

Go need explictily return the error to the upper level function

package errors

import "fmt"

func Main(elements []int32) (int32, error) {
sum, err := sumAll(elements...)
if err != nil {
return -1, err
}

return sum, nil
}

func sumAll(elements ...int32) (int32, error) {
length := len(elements)
if length == 0 {
return -1, fmt.Errorf("empty elements")
}

var total int32
for i := 0; i < len(elements); {
j := i + 1
next := int32(0)

if j < len(elements) {
next = elements[j]

}
sum, err := sum2(elements[i], next)
if err != nil {
return -1, err
}

total += sum

i += 2
}

return total, nil
}

func sum2(a, b int32) (int32, error) {
if b < 0 {
return -1, fmt.Errorf("b is not less than 0")
}

return a + b, nil
}

Unit Test

  • Rust: put the test module under the code that be tested

with

#[cfg(test)]
mod tests {

#[test]
fn test_fn() {
// call test fn
}
}
#[cfg(test)]
mod tests {
use super::sum_nums;

#[test]
fn test_sum_works() -> anyhow::Result<()> {
let sum = sum_nums(vec![1, 2, 3].as_slice())?;
assert_eq!(sum, 6);
Ok(())
}
}

Put unit test code under same package and name with xxx_test.go

package errors

import "testing"

func TestMain(t *testing.T) {
tests := []struct {
name string
args []int32
want int32
wantErr bool
}{
{
name: "happy path",
args: []int32{1, 3, 9, 189, 200},
want: 213 + 189,
wantErr: false,
},
}

for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
got, err := Main(tt.args)
if (err != nil) != tt.wantErr {
t.Errorf("Main() error = %v, wantErr %v", err, tt.wantErr)
return
}

if got != tt.want {
t.Errorf("Main() = %v, want %v", got, tt.want)
}
})
}
}

Collections

Rust

use std::collections::{BTreeMap, BTreeSet, HashMap, HashSet, VecDeque};

pub fn vec(nums: &[i32]) -> Vec<i32> {
let mut num_list = Vec::new();
for num in nums {
num_list.push(*num);
}

num_list
}

pub fn hash_map(keys: &[&str], values: &[&str]) -> HashMap<String, String> {
let mut idx = 0 as usize;
let mut key_val: HashMap<String, String> = HashMap::new();
loop {
if idx >= keys.len() {
break;
}

key_val.insert(keys[idx].to_string(), values[idx].to_string());
idx += 1;
}

key_val
}

pub fn hash_set(elements: Vec<String>, key: &str) -> (HashSet<String>, bool) {
let mut set: HashSet<String> = HashSet::new();
for e in elements {
set.insert(e);
}

let contain = set.contains(key);
(set, contain)
}

pub fn btree_set(elements: Vec<String>) -> BTreeSet<String> {
let mut bs = BTreeSet::new();
for e in elements {
bs.insert(e);
}

bs
}

pub fn vec_dequeue(elements: Vec<String>) -> VecDeque<String> {
let mut queue = VecDeque::new();
for e in elements {
queue.push_front(e);
}

queue
}

pub fn btree_map(elements: Vec<(String, String)>) -> BTreeMap<String, String> {
let mut btree_map = BTreeMap::new();
for (key, val) in elements {
btree_map.insert(key, val);
}

btree_map
}

Golang

package collections

func Array(nums ...int32) [3]int32 {
arr := [3]int32{}
for idx, num := range nums {
if idx > 2 {
break
}
arr[idx] = num
}

return arr
}

func Slice(nums ...int32) []int32 {
numList := make([]int32, 0, len(nums))
numList = append(numList, nums...)
return numList
}

func HashMap(keys []string, values []string) map[string]string {
idx := 0
hashMap := make(map[string]string, len(keys))
for {
if idx >= len(keys) {
break
}

hashMap[keys[idx]] = values[idx]
idx++
}

return hashMap
}

func HashSet(keys []string) map[string]struct{} {
set := make(map[string]struct{}, len(keys))
for _, key := range keys {
set[key] = struct{}{}
}

return set
}

Concurency

Rust Concurrency Programming

use std::{
sync::{
mpsc::{self, Receiver},
Arc, Mutex,
},
thread,
};

pub fn threads() {
let handle1 = thread::spawn(|| {
println!("thread1 running");
});

let handle2 = thread::spawn(|| {
println!("running thread2");
});

let handle3 = thread::spawn(|| {
println!("thread3 running");
});

for handle in vec![handle1, handle2, handle3] {
handle.join().unwrap();
}
}

pub fn sequare_nums() {
let nums = vec![2, 3, 8, 9, 100];
let (rx, tx) = mpsc::channel();
let h1 = thread::spawn(move || {
for num in nums {
rx.send(num).unwrap();
}
});

/*
---- concur::tests::test_sequare_nums stdout ----
calculating num sequare
received: 4
received: 9
received: 64
received: 81
received: 10000
*/
let h2 = thread::spawn(|| {
for num in sq(tx) {
println!("received: {}", num);
}
});

h1.join().unwrap();
h2.join().unwrap();
}

fn sq(nums: Receiver<i32>) -> Receiver<i32> {
println!("calculating num sequare");
let (rx, tx) = mpsc::channel();
let _ = thread::spawn(move || {
for num in nums {
rx.send(num * num).unwrap();
}
});

tx
}

pub fn concurrent_counter() -> i32 {
let counter = Arc::new(Mutex::new(0));
let mut handlers = vec![];

for _ in 0..100 {
let cnt_clone = counter.clone();
let h = thread::spawn(move || {
let mut cnt = cnt_clone.lock().unwrap();
*cnt += 1;
});

handlers.push(h);
}

for handler in handlers {
handler.join().unwrap();
}

let value = *counter.lock().unwrap();
value
}

#[cfg(test)]
mod tests {
use super::*;

#[test]
fn test_channel() {
threads();
}

#[test]
fn test_sequare_nums() {
sequare_nums();
}

#[test]
fn test_concurrent_counter() {
let count = concurrent_counter();
assert_eq!(count, 100);
}
}

Golang Concurrency Programming

package concurrency

import (
"fmt"
"sync"
"sync/atomic"
"time"
)

func RuntThreads() {
go func() {
fmt.Println("running thread1")
}()

go func() {
fmt.Println("running thread2")
}()

go func() {
fmt.Println("running thread3")
}()

// waiting all thread done
time.Sleep(1000 * time.Millisecond)
}

func SendMessage(nums ...int32) []int32 {
inputCh := sendNums(nums)
valuesCh := sequereNums(inputCh)

var result []int32
for val := range valuesCh {
result = append(result, val)
}
return result
}

func sendNums(nums []int32) chan int32 {
numsCh := make(chan int32, len(nums))
go func() {
for _, num := range nums {
numsCh <- num
}
close(numsCh)
}()

return numsCh
}

func sequereNums(input chan int32) <-chan int32 {
valuesCh := make(chan int32, len(input))
go func() {
for num := range input {
valuesCh <- num * num
}
close(valuesCh)
}()

return valuesCh
}

func Counter(initVal int64, times int64) int64 {
var mu sync.Mutex
var wg sync.WaitGroup

var idx = int64(0)
for idx < times {

wg.Add(1)
go func() {
fmt.Printf("adding one to init value\n")

defer wg.Done()
mu.Lock()
initVal += 1
mu.Unlock()
}()
idx++
}

wg.Wait()
return initVal
}

func AtomicCounter1(initVal int64, times int64) int64 {
var wg sync.WaitGroup

var idx = int64(0)
for idx < times {
wg.Add(1)
go func() {
defer wg.Done()
atomic.AddInt64(&initVal, 1)
}()

idx++
}

wg.Wait()
return initVal
}