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_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 — — . , . , . , .








All Articles