📜 ⬆️ ⬇️

What would i change in go

image


For half a year I programmed mostly on Go. And I am disappointed. For two reasons:



I am not the first to take Go in this way. Here are the publications of other people sharing my impressions:



Below I will add my thoughts. To show exactly how Go can be improved, I will compare it with Rust.


Work on Go and Rust began at about the same time: Go was announced in 2009, and Rust in 2010. Both languages ​​use similar approaches:



Probably, both languages ​​were intended to replace C ++: the Go developers claimed that discontent with the complexity of C ++ was the primary motivator for them. Servo is one of the main Rust products of Mozilla. This is a potential successor to the Gecko HTML rendering engine written in C ++.


In my opinion, the key differences between these languages ​​are:



In other words, Rust does not necessarily replace Go. I do not urge everyone who uses Go to switch to Rust. In Rust there is support for real-time operations, if necessary, it is able to operate only with stack memory. In Rust, a complex type system that can, for example, identify problems through a multi-threaded (concurrent) reference to shared data during compilation. All this increases the complexity of Rust programs. The same borrow-checker is famous for its learning curve. But I want to make comparisons with Rust in certain contexts to illustrate options for improving Go. Rust borrowed a lot of good ideas from other languages ​​and competently combined them. And it seems to me that if Go had adopted the same ideas, it would benefit him.


Code samples from the article are available here . You can also take executable tests and experiment there.


Good


I think Go’s is an excellent mechanism for using interfaces to structure data.


I like the separation of behavior from the data itself: structures store data, methods manipulate data in structures. This is a clear separation of state (structures) and behavior (methods). I believe that in languages ​​with inheritance this distinction may not be so obvious.


Go just to study. Object-oriented ideas in it are modified in such a way that they become more accessible to programmers familiar with other object-oriented languages.


Often, a fairly simple way to solve problems “in the style of Go” is used. The same can be said, for example, about Python. The emergence of such stable idioms suggests that any Go programmer will surely understand the code written by any other Go programmer. This is part of the philosophy of simplicity described in the publication Simplicity and Cooperation .


The standard Go library has many carefully thought out features. One of my favorites:


fiveSeconds := 5 * time.Second

, , . . Go : Erlang Scala (actors). Rust (concurrent) .


Rust, , Go. Rust , . — , enum’ (traits). , , . Go, Rust , , (type safety), . : Rust , .


Go. .



Go.


nil


null- , . , nil (bottom-ish type), , (pass-by-reference type).


, nil — null-. , nil. Understanding Nil , , nil , . Go nil. : , , , nil. runtime'a. , , , .


null , , (, Fantom Flow). null. Flow , null, React:


function LoginForm(props) {
  // `?`  `HTMLInputElement` ,  `emailInput`   `null`.
  let emailInput: ?HTMLInputElement

  // JSX-       HTML
  return <form onSubmit={event => props.onLogin(event, emailInput)}>
    <input type="email" ref={thisElement => emailInput = thisElement} />
    <input type="submit" />
  </form>
}

function onLoginTake1(event: Event, emailInput: ?HTMLInputElement) {
  event.preventDefault()

  //  !    `value`  , , , `null`  `undefined`.
  dispatch(loginEvent(emailInput.value))
}

function onLoginTake2(event: Event, emailInput: ?HTMLInputElement) {
  event.preventDefault()

  if (emailInput) {
    //   ,   Flow ,  `emailInput`       `null`  `undefined`.
    dispatch(loginEvent(emailInput.value))
  }
}

null nil , . Go , User. , nil pointer dereference:


func validate(user *User) bool {
    return user.Name != ""
}

Go , , : «… nil». , null, , .


nil Go , nil . (interface value) - , nil, nil true. , nil: , nil. . --nil , (receiver value) nil.


(zero value)? , nil? , — .


Go — , . , , . , Go : , . ++ , . — . (, , ++ , .) , Go ++ . ! , : Rust, Flow . , .


: nil , . (sensible) , nil — . .


: Go (domain-specific types). , (soundness) . (, runtime-) . (pointer types) nil-. , (sensible behavior) . . , .


Go :


. , , - , . , , (, ).


Rust


Rust null-, nil-. enum’. , , , , — . , enum . Option- (Option Pattern). Option Rust:


pub enum Option<T> {
    None,    //   
    Some(T), //    
}

None Some — : , Option<T>. Some , None . Option<T>, (pattern matching), , . (read back) . Some(x), x.


Option- ():


fn checked_division(dividend: i32, divisor: i32) -> Option<i32> {
    if divisor == 0 {
        //     `None`
        None
    } else {
        //    `Some`
        Some(dividend / divisor)
    }
}

#[test]
fn divides_a_number() {
    let result = checked_division(12, 4);
    match result {
        Some(num) => assert_eq!(3, num), //     (bind) `num`
        None      => assert!(false, "Expected `Some` but got `None`")
    };
}

Option- , null, , None Some(None). , , , None , . Some(None) , — None.


Option- , Java. , . Rust Option-, (zero-cost abstractions). Option<T> (reference type), runtime None (null pointer). Some None . , null-.


Option<i32>, i32 . , , Some None. , .


« Rust» .


Go Option-. match , . «» (). , Option.



Go :



func doStuff() error {
    _, err := doThing1()
    if err != nil {
        return err
    }

    _, errr := doThing2()  // Error not propagated due to a bouncy key
    if errr != nil {
        return err
    }

    return nil
}

Rust Result<T,E>, Option<T>. , «» enum’ Result<T,E> — ( E). Result<T,E> Ok(value) ( ) Err(err) ( ).


pub enum Result<T, E> {
    Ok(T),
    Err(E),
}

Option Result - (successful) . . (first-class result values) , , .


Go:


func fetchAllBySameAuthor(postID string) ([]Post, error) {
    post, err := fetchPost(postID)
    if err != nil {
        return nil, err
    }

    author, err := fetchUser(post.AuthorID)
    if err != nil {
        return nil, err
    }

    return fetchPosts(author.Posts)
}

Rust fetchAllBySameAuthor . , , Option Result, — :


fn fetch_all_by_same_author(post_id: &str) -> Result<Vec<Post>, io::Error> {
    let post = match fetch_post(post_id) {
        Ok(p)    => p,
        Err(err) => return Err(err),
    };

    let author = match fetch_user(&post.author_id) {
        Ok(a)    => a,
        Err(err) => return Err(err),
    };

    fetch_posts(&author.posts)
}

match (pattern-match block). (pattern) , , . - Go, switch. Rust . . : , .


Rust , Go. , Result<T,E> Option<T> nil. Rust , .


Rust try!, , . :


fn fetch_all_by_same_author(post_id: &str) -> Result<Vec<Post>, io::Error> {
    let post   = try!(fetch_post(post_id));
    let author = try!(fetch_user(&post.author_id));
    fetch_posts(&author.posts)
}

try! . , try!(fetch_post(post_id)) fetch_post match Ok Err.


try! , Rust : , ?. , let post = try!(fetch_post(post_id)); let post = fetch_post(post_id)?;. ?, .


Go . , Result- . , , (combinator methods):


fn fetch_all_by_same_author(post_id: &str) -> Result<Vec<Post>, io::Error> {
    let post   = fetch_post(post_id);
    let author = post.and_then(|p| fetch_user(&p.author_id));
    author.and_then(|a| fetch_posts(&a.posts))
}

and_then — Result<T,E>. (successful result), , Result<U,E>. — (error result), and_then . and_then then Javascript.


, ? map_err, .


let post = fetch_post(post_id)
    .map_err(|e| io::Error::new(io::ErrorKind::NotFound, e));

, : , — . DRY - . : Rust- . - (recovery code) .


Result<T,E> « » , Option<T>, enum . , Go. Go . Rust T E (, ), , Ok(value) Err(err).


enum’ Rust , Result<T,E> , . Result- Go? , Go (. . ), . , : Go, , ( Go — ). . , .



Go : , . Rust :


fn map<A, B, F>(callback: F, xs: &[A]) -> Vec<B>
    where F: Fn(&A) -> B {

(input slice), , . , (iterator types) Rust , . . , , . , .


Go . , , (top-type) []interface{}. :


func Map(callback func(x interface{}) interface{}, xs []interface{}) []interface{} {
    ys := make([]interface{}, len(xs))
    for i, x := range xs {
        ys[i] = f(x)
    }
    return ys
}

. (, []int) (type-compatible) []interface{}. Map []int. []interface{}, for int. Map , , . Map , (runtime type assertion) (type switch) .


(type-compatible) interface{}. interface{}, :


func Map(callback interface{}, xs interface{}) interface{}

. . , API runtime , . Writing type parametric functions in Go. , . , .


: Filter, Take, Reduce . . , — . Go , Map, , Go. Go .


, , Go . . Javascript, Python Ruby . , . . , Javascript -, . Go : , , .


— — «» . « - ». , , , , the. ( . — . .)


Go (list abstractions). Go:


//      `count`
func LatestTitles(docs []Document, count int) []string {
    var latest []string
    for _, doc := range docs {
        if len(latest) >= count {
            return latest
        }
        if !doc.IsArchived {
            latest = append(latest, doc.Title)
        }
    }
    return latest
}

, , - , . , filter, map, take. Rust:


fn latest_titles(docs: &[Document], count: usize) -> Vec<&str> {
    docs.iter()
        .filter(|doc| !doc.is_archived)
        .map(|doc| doc.title.as_str())
        .take(count)
        .collect()
}

Rust , . Rust . , . , . .


Go. : «, , ». , . Rust «» (lazily-evaluated) . filter, map, take . filter map . , take(count), . , iter, filter, map, take collect — , . , , filter map . « Rust» .


, , . , — . map, , : « (mapping function)». . , for.


(parallel-fetch). Go, :


func (client docClient) FetchDocuments(ids []int64) ([]models.Document, error) {
    docs := make([]models.Document, len(ids))
    var err error

    var wg sync.WaitGroup
    wg.Add(len(ids))

    for i, id := range ids {
        go func(i int, id int64) {
            doc, e := client.FetchDocument(id)
            if e != nil {
                err = e
            } else {
                docs[i] = *doc
            }
            wg.Done()
        }(i, id)
    }

    wg.Wait()

    return docs, err
}

WaitGroup, , , — , - .


, docs. , docs . , . (models.Document, error), Go …


Rust , , , (thread-unsafe) . , , . , Rust (concurrency) .


Go Rust, futures:


fn fetch_documents(ids: &[i64]) -> Result<Vec<Document>, io::Error> {
    let results = ids.iter()
        .map(|&id| fetch_document_future(id));
    future::join_all(results).wait()
}

//  `fetch_document_future` —   .

Rust , Go: , . , . , Rust , . , Rust , , Go .


: map , . .


Rust , fetch_document Future. future::join_all Future. Future Javascript: . Future, wait . , Go, , Future Rust .


Future Stream -. , Stream .


—


Go «» make. , , . Go, . , , (map) . make , . , :


mySlice := make([]int, 16, 32)

. , .


, (overload) make, . .


range. , , :


for idx, value := range values { /* ... */ }  // `range`    
for idx := range values { /* ... */ }  //      

, range . . . .
, ==, >, . .


, . Go. , , (collection types) .


Rust . , , Rust , . Rust , .


, make range — , Rust: , . Rust : . (trait method implementation) , , (, (equality trait) , equals ) .


make Rust:


use std::sync::mpsc::{Receiver, Sender, channel};

// `trait`    
trait Make {
    fn make() -> Self;
    fn make_with_capacity(capacity: usize) -> Self;
}

// `impl`        .
//     `Make`    `Vec<T>`.
//     `Vec<T>`.
impl <T> Make for Vec<T> {
    fn make() -> Vec<T> {
        Vec::new()
    }

    fn make_with_capacity(capacity: usize) -> Vec<T> {
        Vec::with_capacity(capacity)
    }
}

// `Sender`  `Receiver` —    (channel types) Rust.
//       /.
impl <T> Make for (Sender<T>, Receiver<T>) {
    //   Rust     .          // ,   (  ).    
    //   ,     impl.
    fn make() -> (Sender<T>, Receiver<T>) {
        channel()
    }

    fn make_with_capacity(_: usize) -> (Sender<T>, Receiver<T>) {
        Make::make()
    }
}

#[test]
fn makes_a_vec() {
    //    ,    `make`.
    //   ,   `make`  .
    let mut v: Vec<&str> = Make::make();
    v.push("some string");
    assert_eq!("some string", v[0]);
}

#[test]
fn makes_a_sized_vec() {
    let v: Vec<isize> = Make::make_with_capacity(4);
    assert_eq!(4, v.capacity());
}

#[test]
fn makes_a_channel() {
    //      ,    .
    let (sender, receiver) = Make::make();
    let msg    = "hello";
    let _      = sender.send(msg);
    let result = receiver.recv().expect("a successfully received value");
    assert_eq!(msg, result);
}

. : (crate), . ( — Rust.) Rust make, .


make make_with_capacity , Rust (method overloading). Go .



Go . - , «».


,


Scala . Scala (tagged union) . : , .


Rust enum:


use std::sync::mpsc::{Receiver, Sender, channel};
use std::thread;

//    ,     .
//  ,        enum, 
//     .
#[derive(Clone, Copy, Debug)]
pub enum CounterInstruction {
    Increment(isize),  // `isize` —  ,   platform word size
    Reset,
    Terminate,
}

pub type CounterResponse = isize;

use self::CounterInstruction::*;

pub fn new_counter() -> (Sender<CounterInstruction>, Receiver<CounterResponse>) {
    //       /
    let (instr_tx, instr_rx) = channel::<CounterInstruction>();
    let (resp_tx,  resp_rx)  = channel::<CounterResponse>();

    //      
    thread::spawn(move || {
        let mut count: isize = 0;

        //   ,  `recv()`   `Err`
        //  `Ok`,    .
        while let Ok(instr) = instr_rx.recv() {

            //  ,     
            //     .    `enum`  (sealed),
            //      .
            //    runtime-    
            //  ,      .
            match instr {
                Increment(amount) => count += amount,
                Reset             => count = 0,
                Terminate         => return
            }

            // `send`  `Result`,      - 
            //  .        `_`,
            //   .
            let _ = resp_tx.send(count);
        };

    });

    //      
    (instr_tx, resp_rx)
}

#[test]
fn runs_a_counter() {
    let (tx, rx) = new_counter();
    let _ = tx.send(Increment(1));
    let _ = tx.send(Increment(1));
    let _ = tx.send(Terminate);

    let mut latest_count = 0;
    while let Ok(resp) = rx.recv() {
        latest_count = resp
    }

    assert_eq!(2, latest_count);
}

Rust . Drop. , - (cleanup code) . , , resp_tx .


:


match instr {
    Increment(amount) => count += amount,
    Reset             => count = 0,
    Terminate         => return
}

instr, CounterInstruction. CounterInstruction ; match , .


Go , . Go :


switch instr := <-instrChan.(type) {
    case Increment:
        count += Increment.Amount
    case Reset:
        count = 0
    case Terminate:
        return
    default:
        panic("received an unexpected message!")
}

. . Rust , , . , , , , , . Rust- , , .


Go , . , , . Go (sealed): , , , , . ( ) , .



Go . interface{}, . , . . , (unchecked type casts). () , . , .


runtime, . 100%- : , (code path) (type error).


, , , . Rust , (resolves types) . enum’ . , enum’ . .


:



,


Rust (resolve) . , Rust . , — -, , Go. , Rust -. Rust . . « Rust»: , -.


Go . , runtime (runtime reflection), .


Rust . (borrow-checking). Haskell 10 Go. Haskell , Rust Go. ( Rust — (type classes) Haskell.)


Go . , (composition over inheritance) , . . , Go, .



Go . ( Rust) , «». , . Go .


Make new_counter. :


//  
// (`isize` —  ,  platform word size)
fn min_and_max(xs: &[isize]) -> (isize, isize) {
    let init = (xs[0], xs[0]);
    xs.iter()
        .fold(init, |(min, max), &x| (cmp::min(min, x), cmp::max(max, x)))
}

#[test]
fn consume_tuple() {
    let xs = vec![1, 2, 3, 4, 5];
    let (min, max) = min_and_max(&xs); //      
    assert_eq!(1, min);
    assert_eq!(5, max);
}

- Go . , , :


// 
results := make(chan (*models.Document, error))

, struct, interface{} . .


Go — , . Go and_then Rust.



Go. « » , Go .


kachayev , . .


Rust, , -. futures - Tokio. - . « Haskell»:


, ( ) . — , .

— , (multiple threads of control). . , . , . .

Go . Rust , .


, Map-Reduce. (list abstractions). Rust Map-Reduce Rayon:


//   `par_iter`   .
// `par_iter` —   Rayon,  Rayon  
//       (standard slice types).
use rayon::prelude::*;

pub fn average_response_time(logs: &[LogEntry]) -> usize {
    let total = logs.par_iter()
        .map(|ref entry| entry.end_time - entry.start_time)
        .sum();
    total / logs.len()
}

map, (job queues), (worker pool). map . , , . , . , Rayon (batches), map. ( — 5000 , .) sum ( — Reduce) Rayon. , (worker threads).


. . .



Go 2.0 ? . . , Go , nil, . Rust . (receiver-less methods). , , Go.


Go . , . , . Go, , .


, , Rust: , Go, , «».


Javascript , Go, ( ). Flow Typescript Go. , , Javascript Flow.


Erlang Scala , Go. .


Clojure , ! . Clojure .


Haskell , . . Haskell «».


, . Go — — . , . , . , .


')

Source: https://habr.com/ru/post/325046/


All Articles