🦍

GopherがRust入門したので違いをまとめてみた

に公開
9

Discussion

magurotunamagurotuna

ものすごい網羅量で圧倒されました。良質なまとめをありがとうございます!

脚注6番に

tokioでmutexを使う場合はtokio::sync::Mutexを使う必要がある

とありますが、このコード例であれば std::sync::Mutex でも問題なく動くかなと思います。
また、Mutex in tokio::sync - Rustによると、

Contrary to popular belief, it is ok and often preferred to use the ordinary Mutex from the standard library in asynchronous code.

The feature that the async mutex offers over the blocking mutex is the ability to keep it locked across an .await point. This makes the async mutex more expensive than the blocking mutex, so the blocking mutex should be preferred in the cases where it can be used. The primary use case for the async mutex is to provide shared mutable access to IO resources such as a database connection. If the value behind the mutex is just data, it’s usually appropriate to use a blocking mutex such as the one in the standard library or parking_lot.

ChatGPT訳(ちょっと改変あり)↓

一般的な認識とは異なり、非同期コードでも標準ライブラリの通常のMutexを使用することは問題なく、むしろ推奨されることがよくあります。

非同期MutexがブロッキングMutexに比して提供する機能は、.awaitポイントをまたいでロックを保持できることです。このため、非同期MutexはブロッキングMutexよりも高価になります。そのため、使用可能な場合はブロッキングMutexを優先するべきです。非同期Mutexの主な用途は、データベース接続などのIOリソースに対して共有の可変アクセスを提供することです。Mutexの背後にある値が単なるデータである場合、標準ライブラリやparking_lotのようなブロッキングMutexを使用するのが通常適切です。

とあるので、今回のコード例のようにロックが.awaitをまたがないケースであれば std::sync::Mutex (あるいは parking_lot::Mutex) を使うことが望ましいようです。

syumaisyumai

非常に充実した記事をありがとうございました!
Rustはほんの少しだけ書いたことがあるだけだったので忘れたことや知らなかったことも多く、
普段よく書いているGoと比較しながら復習できるので、辞書的にも使えるとてもありがたい資料だと思いました!とても面白かったです…!
いくつか、Go側について少しだけ補足的にコメントさせていただきます!


GoとRustのジェネリクスの違いについて

次のように、Goではインターフェイスを定義る必要があります。

についてですが、

実は、インラインでinterfaceを書くこともできます。

func write[T interface { Writer; Closer }](wc T, data []byte) {
	defer wc.Close()
	wc.Write(data)
}

https://go.dev/play/p/15k24jOicYo

ただ、「interface elementが一つのみかつ、メソッドではなくtype elementのみが含まれる」ケースで使える省略記法 ([T string|int] のようなもの) が使えないので、記法としてはやや無骨にはなってしまいますね…。

テストの実行について

Goのテストは基本的に直列ですが、t.Parallel()を使えば並行で実行できます。

(基本的にと書かれているので余計かもしれませんが、) 一応、別packageのテストについてはデフォルトで並列に実行されるようです。
デフォルトの並列数は、利用可能なCPU数となります。

参考: https://engineering.mercari.com/blog/entry/how_to_use_t_parallel/

複数のパッケージのテストを指定した場合、パッケージ単位でテストが並列に実行されます。たとえば、aパッケージとbパッケージがあった場合、aパッケージ内のテストコードは逐次実行され、bパッケージ内のテストコードも逐次実行されます。しかし、aパッケージとbパッケージのテストは並列に実行されます。

全てのテストをデフォルトで並列に実行するRustの振る舞いが理想的なのは完全に同意です (Goだとそれをデフォルトの挙動にしたら競合しまくるので…)


改めて、非常に参考になる記事をありがとうございました!

ゴリラ@1月から本気ダイエットしますゴリラ@1月から本気ダイエットします

@syumai

実は、インラインでinterfaceを書くこともできます。

おお、知らなかったです!
勉強になります

(基本的にと書かれているので余計かもしれませんが、) 一応、別packageのテストについてはデフォルトで並列に実行されるようです。
デフォルトの並列数は、利用可能なCPU数となります。

コレも知らなかったです!
ありがとうございます!

諸々追記しておきました!

Q/AQ/A

大変素晴らしい記事ありがとうございます!

Rust がコンパイル時に型ごとのコードを生成しますとあってGoはそうでないように見えますが、
Goもgo shape の後にコード生成されるので実際のアセンブリコードは

それぞれ別のアセンブリに生成されているので同じことだと思います。


(アセンブリの [ ] はただの名前に括弧が入っているだけです)

ゴリラ@1月から本気ダイエットしますゴリラ@1月から本気ダイエットします

Rust がコンパイル時に型ごとのコードを生成しますとあってGoはそうでないように見えます

自分の理解が間違っているようですが、desgin doc を読んで改めて調べてみます
ありがとうございます!

mythrnrmythrnr

大変な力作尊敬します。すごい...

一点、表現の問題だとは思いますが、 テストの記述について のところにあった記述が気になりました。

また、ファイル内にテストを書くということで、プライベートな関数のテストも書けます。
ここがGoとの違いの1つですね。

同一ファイル内に書くかどうかは異なりますが、Go でもテストファイルのパッケージ名を同一にすることでプライベートな関数のテストが書ける点、補足できればと。(Go でプライベートな関数のテストが書けないという意味で書かれたわけではないと思いますが、念の為...)

a.go
package hoge

func foo() int {
	return 1
}

type bar struct {
	a int
}

func newBar() *bar {
	return &bar{1}
}

func (b *bar) inc() {
	b.a += 1
}
a_test.go
package hoge

import "testing"

func TestFoo(t *testing.T) {
	if foo() != 1 {
		t.FailNow()
	}
}

func TestBar(t *testing.T) {
	v := newBar()
	if v.a != 1 {
		t.FailNow()
	}

	v.inc()

	if v.a != 2 {
		t.FailNow()
	}
}
cybergaragecybergarage

秀逸な記事ありがとうございます!

ざっと確認した限りですが、ドキュメントテストについては、GoでもRust相当のことはできるのではないでしょうか?

Goのドキュメントテストの特徴としては

  • 試験方式については、標準出力形式でサポート
    • Exampleの表示から試験は見えなくできる
    • Rustは基本コンパイルでのサポート (試験にはassertが必要)
  • 命名規則によりGodocにより収集され表示される
    • 記述位置の自由度が高い、複数サンプル記述も可
    • Rustは、直前の単数記述のみに限定(?)

といったところでしょうか。ご参考ください。