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.
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.
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 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
.
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
. , .
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’ . .
:
unsafe
, Rust . . unsafe
, . unsafe
. .nil
. , -, , , , .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.
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