Goで何を変更しますか

画像


6か月間、主にGoでプログラミングしました。 そして、私は失望しています。 次の2つの理由から:



このようにGoを認識するのは私が初めてではありません。 私の印象を共有している他の人々は次のとおりです。



以下に私の考えを追加します。 Goを改善する方法を示すために、Rustと比較します。


GoとRustの作業はほぼ同時に始まりました。Goは2009年に、Rustは2010年に発表されました。 どちらの言語も同様のアプローチを使用します。



どちらの言語もおそらくC ++を置き換えることを目的とていました。Go開発者は 、主な動機はC ++の複雑さに対する不満であると主張しました。 Servoは、MozillaのコアRust製品の1つです。 これは、C ++で記述されたGecko HTMLレンダリングエンジンの後継となる可能性があります。


私の意見では、これらの言語の主な違いは次のとおりです。



つまり、Rustは必ずしもGoに取って代わるものではありません。 Goを使用してRustに切り替えることはお勧めしません。 Rustはリアルタイム操作をサポートしており、必要に応じてスタックメモリのみを処理できます。 Rustには洗練された型システムがあり、たとえば、コンパイル中に共有データへの同時アクセスを通じて問題を特定できます。 これにより、Rustプログラムの複雑さが増します。 同じ借用チェッカーは学習曲線で有名です。 しかし、特定のコンテキストでRustと比較して、Goを改善するためのオプションを示したいと思います。 Rustは他の言語から多くの優れたアイデアを借用し、それらを適切に組み合わせています。 そして、Goが同じアイデアを採用した場合、それは彼にとって良いことだと思います。


この記事のコード例はこちらから入手できます 。 そこで実行可能なテストと実験を行うことができます。


いいね


インターフェイスを使用してデータを構造化するには、Goが優れた方法であることがわかりました。


データ自体からの動作の分離が好きです。構造はデータを保存し、メソッドは構造内のデータを操作します。 これは、状態(構造)と動作(メソッド)の明確な分離です。 継承のある言語では、この違いはそれほど明白ではないと思う。


学ぶためだけに行ってください。 その中のオブジェクト指向のアイデアは、他のオブジェクト指向言語に精通したプログラマーがよりアクセスしやすくなるように修正されます。


多くの場合、かなりシンプルな「ゴースタイル」ソリューションが使用されます。 たとえば、Pythonについても同じことが言えます。 このような永続的なイディオムの出現は、Goプログラマーがおそらく他のGoプログラマーによって書かれたコードを理解することを示唆しています。 これはSimplicity and Collaborationで説明されている単純化の哲学の一部です。


Go標準ライブラリには、多くの精巧な機能があります。 私のお気に入りの1つ:


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_thenResult<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/J325046/


All Articles