Rust part25

■ このスレッドは過去ログ倉庫に格納されています
2024/07/31(水) 00:46:26.17ID:DBMWY2QT
公式
https://www.rust-lang.org/
https://blog.rust-lang.org/
https://github.com/rust-lang/rust

公式ドキュメント
https://www.rust-lang.org/learn

Web上の実行環境
https://play.rust-lang.org

※Rustを学びたい人はまず最初に公式のThe Bookを読むこと
https://doc.rust-lang.org/book/

※Rustを学ぶ際に犯しがちな12の過ち
https://dystroy.org/blog/how-not-to-learn-rust

※Rustのasyncについて知りたければ「async-book」は必読
https://rust-lang.github.io/async-book/

※次スレは原則>>980が立てること

前スレ
Rust part24
https://mevius.5ch.net/test/read.cgi/tech/1716759686/

ワッチョイスレ
プログラミング言語 Rust 4【ワッチョイ】
https://mevius.5ch.net/test/read.cgi/tech/1514107621/
2024/08/12(月) 13:11:33.58ID:XQ/hRBSk
>>117
保守には良いと思う
リファクタリングには向いていない
2024/08/12(月) 13:21:38.56ID:XQ/hRBSk
>>120
ベアメタルって元々そういうもんでそ
2024/08/12(月) 14:00:36.88ID:zYUAXUFL
>>121
ちゃんとトレイトベースで作っていれば
Rustはリファクタリングもしやすくミスも防げる
2024/08/12(月) 14:04:58.34ID:s7amXorj
>>123
「ちゃんとトレイトベースで作っていれば」
というのが難物という罠。
2024/08/12(月) 14:12:42.26ID:zYUAXUFL
トレイトを使いこなせば便利で快適なのに
難しいと思い込んで使いこなさない人が困るだけ
2024/08/12(月) 14:53:33.48ID:1PYqSgWq
複オジに餌を与えないでください!
みんなが迷惑しています!
2024/08/12(月) 15:10:33.91ID:CLy07uUA
事前に理想的な設計が出来るなら苦労はないんだが現実はそうではないという話だわな
2024/08/12(月) 15:15:42.43ID:YiYv/cy+
新たにtraitを導入する時やリファクタリングで構成を変える時も新たなtrait境界を満たしていけばよくて使い勝手がいいね
2024/08/12(月) 15:16:59.31ID:KoHKVvuj
リファクタリングができない難しいという
人が定期的に出るけど他の言語とも比較して
コード例出してくれ
130デフォルトの名無しさん
垢版 |
2024/08/12(月) 15:32:17.85ID:c1LudIob
リファクタリングしやすいしにくいの違いの定義がそもそもわからん
そういう意味でも例を頼む
2024/08/12(月) 16:20:20.18ID:IbEOTvl2
自分的には「リファクタリングしやすい」=「書き換えミスがコンパイルエラーとして検出されやすい」なので
型強めの静的型(Rust)>型弱めの静的型(C系)>動的型
って感じ
ただ「書き換え時にコンパイルエラーに煩わされない」を重視してて動的型がリファクタリングしやすい派もいるっぽい
(個人的にはエラーにならなくてもいつのまにか動作変わってたら意味なくない?と思うけど…)
2024/08/12(月) 17:02:05.11ID:Hka8sI98
ああ,書き換え中にエラーなり警告なりが出るのが煩わしいってことか
大抵の言語でそれはありそうだから,エディタの設定で最後にタイプしてから何秒後にアナライザのチェックを入れるかとかいじればいいのでは
2024/08/12(月) 17:13:40.85ID:rd9pnszR
JetBrainsがリファクタリング機能の出来具合で比べてみると一目瞭然
静的型付け言語の中では最弱
2024/08/12(月) 17:14:55.12ID:s11pSEyp
前スレでもリファクタリングの話出てたけど
無かったことにしてまた同じ話するの
2024/08/12(月) 17:16:15.95ID:CLy07uUA
プログラマの中には剛腕タイプというか、脳のメモリが大きくて広い範囲の見通しを付けられる人はいる。
そういう人にとっては抽象レイヤも静的型も邪魔になる。

でもまあそういうのはほんまもんの超人なので普通の人は静的型のほうがいいと思う
2024/08/12(月) 17:23:31.41ID:s11pSEyp
他言語との比較もネタ切れ感無理矢理感が激しくていい加減飽きてきた
2024/08/12(月) 17:32:40.07ID:hWGH0NP6
昔から静的型付け言語はリファクタリングが不便と主張する人がいるけど
イヤなら動的型付け言語を使っていなさい、で終わる話
138デフォルトの名無しさん
垢版 |
2024/08/12(月) 20:58:59.97ID:+jMHtzbv
Rustの面倒臭さって静的型付けだけでないと思う
方針が見えてるものを堅く作る分には強いけど、作る→試す→改善するといったサイクルを回すような開発は負荷が大きい
Rustでのゲーム開発を断念したLogLog Gamesが出した文章 (https://loglog.games/blog/leaving-rust-gamedev/) なんかは、全てに同意しないにしても、Rustってそういうデメリットはあるよねって感じるだろうし
2024/08/12(月) 21:02:06.81ID:Bgv7f5Pf
matchで全揃い(Some(a), Some(b), Some(c)) =>などができない言語だと確かに面倒かもしれない

484 デフォルトの名無しさん 2024/08/12(月) 17:05:54
Optional型の変数はNoneの場合のハンドリングが必要だから余計に複雑度が増す
だからNoneで明示的に初期化が必要な関数ローカルの状態変数が3つもあるのなら
リファクタリングを検討したほうがいいと思ってる
2024/08/12(月) 21:06:34.61ID:P+uhzEsZ
>>138
その人も「Once you get good at Rust all of these problems will go away」と書いているように
Rust流のやり方を身に着ければ消える問題ばかり
2024/08/12(月) 21:13:39.00ID:s11pSEyp
https://github.com/rust-lang/rust/issues/91639

デフォルトでwarnにもdenyにもなってないけど最重要級のlint見つけたぞい
前スレのライフタイムリファクタの話題が出た時点で教えて欲しかったな……
142デフォルトの名無しさん
垢版 |
2024/08/12(月) 21:19:15.69ID:+jMHtzbv
>>140
内容読んでるか?
その反応こそ批判対象の一部だぞ
Rustの根本的な部分で困難が生じていても、Rustコミュニティは「それはあなたがRustのやり方を知らないだけだ」としか言わない、というのを批判してるチャプターだ
2024/08/12(月) 21:32:50.85ID:eisITTDv
複オジをRustコミュニティの代表者として扱うなよ
2024/08/12(月) 21:42:37.55ID:3Ibpt2aV
>>141
それよりも簡単な解決方法として
ライフタイム'1と'2の相違エラーが出た時点で
その部分の省略ライフタイムを自分で明示的に補ってやればいい
そうすると何が問題だったのかが視覚化される
2024/08/12(月) 22:04:56.11ID:AOmsGFzU
>>139
Pythonでも普通に出来るぞw
論点そこじゃないだろww
2024/08/12(月) 22:12:53.72ID:s11pSEyp
他言語と比較して優位を示したい向きがメインになってしまうとどうも Rust の問題の存在を否定する方向に向かいがちで良くないな
そう考えるとオナニーコードでレスバやってたほうがいくらかマシだったのかもしれん
2024/08/12(月) 23:01:06.12ID:ksJqJtTC
リファクタリングの件でも他の件でもいいけど、
他の言語と比較してRustで何が難しくて困っているのかを具体的なコード例で出してくれないと、
話が進まずに個人の感想か空想に終わってしまう。
2024/08/13(火) 12:11:44.59ID:Nsck/Z03
>>131
ミスをコンパイルエラーにしてくれることがプログラム書いて色々試してる時もリファクタリングしてる時も一番重要だもんな
他の言語だと実行時のエラーや実行時のデバッグに依存せざるを得なかったことがRustでは実行前に解決することが多くて開発効率が良くて助かる
149デフォルトの名無しさん
垢版 |
2024/08/13(火) 12:58:50.63ID:qGcIneKd
設計を変更する際に必要な変更が多いのはありそう
例えば既存のコードをasyncに対応させようとすると、必要なデータを Arc, Mutex で包んだり、参照のせいでSendできないものをOwnedな型に置き換えたり、トレイト境界に Sync + Send を付けてまわったりといった手間が要る
「設計を変更する」と決めたのでなく、「変更するとどうなるか試したい」といった場合でもこういった作業が必要で、その点は面倒かもしれない
必要な変更をコンパイラが教えてくれるのがありがたいというのは自分も同意する
2024/08/13(火) 13:12:20.55ID:J7fCsAWj
>>149
シングルスレッドの言語以外は
メモリを共有するならそれら排他制御が必要となる点で言語に関係なく同じ
メモリを直接共有しない(例えばチャネル使用)ならRustでもそれらは必要ない
つまりRust特有の話ではない
2024/08/13(火) 13:12:37.07ID:iXbJ0ifY
動的言語に比べて静的言語がリファクタリングしやすいのは当たり前
モダンな言語の中でRustは所有権とライフタイムのせいで他に比べて明らかにリファクタリングが面倒

火を見るより明らかなことなのになぜ必死に否定したがるのか意味がわからない
2024/08/13(火) 13:14:54.05ID:6c4LDvBa
>>149
asyncはリファクタリングとはまた別のRustの弱点
2024/08/13(火) 13:23:07.46ID:MGOQRx4E
>>152
Rustのasyncはスタックレスでリソース消費も少なく高速で動作して使い勝手もいいよ
何を根拠に弱点と?
2024/08/13(火) 13:28:41.46ID:+eZNFqL6
>>151
所有権とライフタイムは慣れの問題であり大した問題ではない。
それが出来ない人はGCに依存して遅くメモリ喰いの言語を使っていなさい。
155デフォルトの名無しさん
垢版 |
2024/08/13(火) 13:46:36.22ID:qGcIneKd
非同期による排他は別にしても参照まわりの面倒さはある
&str にするか String にするか、Rc<String> なのか Arc<Mutex<String>> なのかといったものを考える必要があるし、リファクタ/設計変更の際にこれらの置き換え作業が必要になることはある
細かな最適化はできなくても、GC有りの言語がシンプルに1つString型だけ提供しているのは楽といえば楽

なぜエラーになるのかはコンパイラが教えてくれるし、慣れれば分かるものだけど、必要な変更が他の静的言語に比べて多くて面倒というのは否めないでしょ
自分はRustは好きだし良い言語だと思ってるけど、不便が無いとは思わないし、それが分からないのは現実の開発経験が無いんじゃないかとすら思う
2024/08/13(火) 13:51:51.56ID:FRB3Y3s9
Rustはわかってないとそもそも型チェックに通るコードが書けない
2024/08/13(火) 14:03:17.07ID:vwwzn05u
もっと経験を積むと「なぜエラーになるのかはコンパイラが教えてくれる」どころか「コンパイラはこちらのミスをエラーにしてくれる」すら思い込みだったと気づく時が来る
もう一度貼っておくからよく読むように
特に「5) コンパイルされたならライフタイムの記述は正しい」と「7) コンパイラのエラーメッセージはプログラムの直し方を教えてくれる」

https://github.com/pretzelhammer/rust-blog/blob/master/posts/translations/jp/common-rust-lifetime-misconceptions.md
2024/08/13(火) 14:04:43.72ID:SFyZa65C
Rustの問題じゃないね
チーム適用や実要件に当てはめて見る実務マと趣味マの違い
2024/08/13(火) 16:05:15.51ID:d5oxWZvl
>>157
それは詭弁
例えばその「5) コンパイルされたならライフタイムの記述は正しい」
まずこれは参照の安全性を保証するRustとしては常に正しい

もちろん複数の参照が登場する時は色んな組み合わせのライフタイムの付け方がある
それらのうち参照の安全性を保証できる組み合わせのみをコンパイラは通す
コンパイルが通る組み合わせのうちどれが自分の意図なのかは各自の問題となる

もし自分の意図と異なっていた場合はそれが利用されるところで参照が切れてコンパイルエラーとなる
そのため間違っていたことに気づくことができる
そして修正してコンパイルが通れば記述は正しい
その記事の例でもこの流れで正しい記述にたどり着けている
160デフォルトの名無しさん
垢版 |
2024/08/13(火) 17:41:07.28ID:3jlS4CIE
「Rustで作るプログラミング言語」
この本おすすめですか?
2024/08/13(火) 17:55:28.98ID:vwwzn05u
>>159
利用の仕方が変わると、それに引っ張られて宣言側が正しかったかどうかが変わる、と言いたいの?
2024/08/13(火) 18:03:57.04ID:d5oxWZvl
>>161
一般的にそうなる
例えば二つの参照を引数にとる関数で二つに異なるライフタイムを持たせるかどうかは利用の前提や方針でどちらもありうる
利用する側(やそれを全て想定したtest)でコンパイルが通るならばそのコードは正しい
163デフォルトの名無しさん
垢版 |
2024/08/13(火) 18:21:49.94ID:qGcIneKd
「参照を扱う際は適切なライフタイム注釈を書く必要がある」は他の静的言語より面倒という指摘そのものでは?
「適切なライフタイム注釈を書けばコンパイルは通るし、それで安全性が保障される」のは分かってるけど、問題はそこではなくて
2024/08/13(火) 18:23:56.90ID:vwwzn05u
>>162
なるほどね、「正しいコード」の定義に齟齬があるのね

「5) コンパイルされたならライフタイムの記述は正しい」の「正しい」は「プログラマの意図通りである」の意味だと思って
それを念頭にもう一回読み直してみて
2024/08/13(火) 18:27:02.24ID:6FUbHmIE
>>163
それはRustの問題じゃないよ
GC言語か否かの問題だね
GC言語は参照の有効性を気にしなくてもよい代わりに遅くてメモリを消費するペナルティ
2024/08/13(火) 18:33:34.80ID:d5oxWZvl
>>164
想定する利用ケースでコンパイルが通ればプログラマの意図通りで正しい
>>157の(5)の例もプログラマの意図通りでなかったためコンパイルエラーとなった
そしてコードを修正してコンパイルを通すことで意図通りの正しいコードになった
2024/08/13(火) 18:35:53.44ID:G2fhNxh+
「正しい」という言葉を多用する技術者にまともなやつはいない
168デフォルトの名無しさん
垢版 |
2024/08/13(火) 18:48:09.58ID:qGcIneKd
>>165
C/C++に比べても面倒では?
設計で防げる問題/考慮が要らない問題に対して必要以上のガードを書かされる感じ

例えばグローバルな変数 (一度だけ初期化され、以降は不変) に対して Sync が要るのも、初期化が複数スレッドから呼ばれた場合の安全性を保証しないとコンパイルが通らないからだけど、設計上それは要らないって場面はある
シングルスレッドなアプリだったり、マルチスレッドだけどスレッド生成は初期化の後であるいった場合

参照も同じで、設計上無効なデータを指すことがないといえるケースでも、コンパイラを納得させるために手動でライフタイム注釈を書く必要があるといった感じ
2024/08/13(火) 18:49:04.65ID:d5oxWZvl
>>167
>>157の「5) コンパイルされたならライフタイムの記述は正しい」が合っているかどうかの話が行われている
想定した使い方でコンパイルが通ったならば記述は正しい
何をどう想定するかでコードは変わりうる
2024/08/13(火) 18:59:01.23ID:6FUbHmIE
>>168
スレッド内でしか用いないならばthread_localにより!Syncでも使えるよ
「設計上それは要らない」「設計上無効なデータを指すことがない」という思い込みは絶対にダメ
例えば無効なデータを指さないが&'staticの意味ならばそれを使えばよいのだよ
2024/08/13(火) 19:01:54.22ID:vwwzn05u
>>166
想定するケースが最初から全部書けるならそれでいいが、残念ながらそんな現実は無いよ
ソフトウェアテストの7原則のひとつ: 全数テストは不可能

それに、意図はコード化できるものばかりではない、unsafe関数のdoc-commentに書くべきsafety sectionだってそうだろう
コード化できなければ「プログラマの意図」ではないというのならそれも一理あるだろうが、多分誰も同意しないと思うぞ
2024/08/13(火) 19:10:37.01ID:d5oxWZvl
>>171
そんな大きな話はなされていない
一般的な話はRustの範囲を超えている
今回はRustのライフタイムの記述が正しいかどうかの話
想定した使い方でコンパイルが通ればそのコードは正しい&安全となる
想定範囲を変えたり広げたりしてコンパイルエラーとなったならばそれに対応すればよい
173デフォルトの名無しさん
垢版 |
2024/08/13(火) 19:44:50.08ID:qGcIneKd
そもそもの話は「他の言語に比べてリファクタや設計変更がしづらい」という話では?
誰も「ライフタイムが提供する安全性が間違ってる」なんてレスはしてないよ
安全性の保証は強力だけど、そのぶん生産性を低下させ得るデメリットもあるよねって話をしてるだけで
2024/08/13(火) 19:53:38.42ID:vwwzn05u
>>172
あなたが「コンパイルが通ればそのコードは正しい」というとき、それは利用側と定義側を含むコード全体の正しさのことを言っているんだよね

でも例のブログ記事は、利用側のfn main()の本体に何が書かれていようと、定義側のfn next()はそれ単独で「正しい」か(≒プログラマの意図通りか)を考えられる前提で書かれているんですよ
つまりmain()でnext()を1回呼ぼうが2回呼ぼうが、next()は変えていないんだから想定する意図は変わらない
最初からnext()は間違っていたことが、main()を変えたことで明らかになった、と

そう思ってもう一回読んでみてね?
175デフォルトの名無しさん
垢版 |
2024/08/13(火) 19:55:05.46ID:qGcIneKd
Dioxusのチームもこのような記事を書いている
https://dioxus.notion.site/Dioxus-Labs-High-level-Rust-5fe1f1c9c8334815ad488410d948f05e

GitHub Acceleratorに選ばれるくらい有望なプロジェクトだし、間違いなくレベルの高い開発者が揃ってるチームけど、彼らですらRustの課題を指摘してるわけで
2024/08/13(火) 19:56:59.13ID:6FUbHmIE
>>173
ライフタイムをコンパイラがチェックしてくれることで生産性は低下ではなく上昇でしょ
全てのコードを人間がチェックし続けたりチェックし忘れて実行時デバッグとなることが生産性の低下ですね
さらにその参照バグにきづかないままだとセキュリティホールを招いてしまいますよ
177デフォルトの名無しさん
垢版 |
2024/08/13(火) 19:57:14.06ID:w/VnRva3
>>157
めっちゃ判りますωωω
2024/08/13(火) 20:05:14.94ID:d5oxWZvl
>>174
最初のコードもコンパイラが通ってアサートも通って意図通りに動いている
次のコードはイテレータが返した値を保持したままイテレータを作動させられる仕様への変更 (今回の例は可能だが必ずしも仕様変更できるかどうかは別)
それぞれコンパイルが通れば意図通りに正しく動作している
179デフォルトの名無しさん
垢版 |
2024/08/13(火) 20:07:43.05ID:w/VnRva3
>>174
ほんそれ
2024/08/13(火) 21:30:24.68ID:vwwzn05u
>>178
アサートも利用側のmain()にあるものなんだから、next()の呼び出し回数の話と同じで
ここからnext()についてのプログラマの想定を読み取ってたら、この記事が理解できないのは当然なんだってば
最初から理解する気が無くてそういう意地悪言ってるんじゃないよね?

修正後のプログラムの後に
> 今こうして前のプログラムを見返してみると、明らかに間違っていましたね。
って書いてあるんだから、これは仕様を変更したじゃなくて、最初から間違っていたのを修正したという体なんですよ
2024/08/13(火) 21:51:35.56ID:d5oxWZvl
>>180
一番最初に書いたようにその人の詭弁
後者の利用方法に対応する仕様にしたいならば最初からその後者用のアサートを入れたらよい
そうすれば一発でコンパイルエラーになって修正して終わり
ライフタイムに関してはコンパイルが通ればその利用方法については必ず正しい
コンパイルが通ったのに間違っていることはない
2024/08/13(火) 22:37:13.69ID:d5oxWZvl
もう少しわかりやすい別の例を出すと

struct Foo(&str, &str);
このライフタイム注釈が無いのは当然エラーになるのは置いとくとして

struct Foo<'a>(&'a str, &'a str);
このように宣言しても多くの用途で困ることはなく
むしろこれで都合がよいこともある
つまりこの宣言方法はバグではない
そしてこれでコンパイルが通る範囲の利用方法をしていれば正しく動作する

しかし用途によっては上記では困りエラーとなる利用方法もある
そこで
struct Foo<'a, 'b>(&'a str, &'b str);
このように宣言することで新たな利用方法に対応が可能となる
こちらに変えてもそれでコンパイルが通る範囲の利用方法をしていれば正しく動作する

どちらのケースでもコンパイラが通せば各々の利用範囲でライフタイムの記述は正しい
コンパイラが通したのに間違っているなんてことは起きない
2024/08/13(火) 22:44:00.39ID:vwwzn05u
>>181
じゃあ別の切り口から
next()を単独で見てみようか
結局、問題の根本は戻り値のbyte(=&self.remainder[0])としてあり得る最大のlifetimeは'remainderなのに、それより小さい'mut_selfを指定してしまっていることだよね
trait実装の場合にはtraitの宣言に合わせるために実際より小さいlifetimeを指定しないといけない場合があるしれないが、この例は単なるミスで'mut_selfでなくてはいけない理由は特に無いということだった

このnext()の定義を単独で見て、おかしなところは本当に何も無いのか?
next()が正しくないということを示すために、main()の内容を考慮する必要が本当にあるのか?

別にコンパイルエラーになることをいちいち確認しなくても、「なんかおかしいなこれ」って早く気付いて修正できるならそのほうがいいでしょ、って個人的には思うけどね
2024/08/13(火) 22:54:24.44ID:d5oxWZvl
こちらの主張は最初から一貫して
>>157の記事「『5) コンパイルが通ればライフタイムの記述は正しい』は間違っている」
という主張とその説明例は詭弁だということ

以下は常に正しい
『5) コンパイルが通ればライフタイムの記述は正しい』

自分の意図通りの使い方をした利用範囲でコンパイラが通ったのならば
その利用範囲で記述は正しい
利用するつもりのない利用方法に必ずしも対応する必要がないことは>>182で説明した
2024/08/13(火) 23:11:58.95ID:vwwzn05u
>>184
ここまでのあなたの主張は正確に言えば「*十分に実際のユースケースを反映した利用側コードとともに* コンパイルが通ればライフタイムの記述は正しい」じゃないか
強い前提を置けば正しいと言える確度も高まって当然だろう
勝手に前提を変えて違う結論を導くのは、それこそ詭弁ではないか
2024/08/13(火) 23:29:33.49ID:d5oxWZvl
利用方法とその範囲は各実際のケースで各々異なるのだよ
あらゆるケースを想定して対応することは必要ないだけでなく事実上無理なこともあるだろう
必要となるケースのみ対応すればよい
そして必要となるケースはプログラムとして書かれているかテストコードに書く
どのようなケースでもコンパイルが通ればその記述コードは正しい
コンパイルが通ったのに誤動作することは起きない
2024/08/13(火) 23:29:58.94ID:vwwzn05u
next()で戻り値のlifetimeを不必要に小さくしているのが、単独で見て「普通に考えるとおかしい」と言えるように
>>182の構造体の例も、「普通は」struct Foo<'a, 'b>(&'a str, &'b str);のように分けるべきだろう
例外は実際に.0も.1も同じStringの部分列であることを想定しているような、同じlifetimeを持つ参照から作ることが想定されている場合か、他にもあるかもしれんが

あと例の「Rustのライフタイムについてのよくある誤解」にもある通り、こういう具体的なメンバを想定したlifeitme in pathには'inputとか'bufとか説明的な名前を付けるべきである
そうすれば「あれ、よく考えたらこの.0と.1って同じlifetimeで本当にいいのか」ってことを、利用側でコンパイルエラーになるのを待たず考慮できるとは思わんのかね
'aみたいなテキトウな名前のlifetimeが許されるのは汎用的なコンテナくらいだろう、Iter<'a, T>とかRef<'a, T>とかCow<'a, T>とか

そういう嗅覚というか、経験知みたいな情報は誰も持ち合わせちゃいないのかね
2024/08/13(火) 23:36:10.30ID:Hu5uSedp
複オジに何を言ったところで時間の無駄
そろそろ気付いてね
2024/08/13(火) 23:40:06.70ID:d5oxWZvl
同じところで生まれるものしか取り扱わない場合にFoo<'a, 'b, 'c, 'd, 'e>とする必要はない
そこをFoo<'a>でコンパイルが通る使い方をしているならばFoo<'a>で正しい
どちらかが間違っているという変な考えを捨てよう
2024/08/13(火) 23:46:03.09ID:vwwzn05u
>>188
反論を通じて考えを整理しているという面もあるので
マサカリ歓迎
2024/08/13(火) 23:55:49.36ID:LZM61rZJ
>>175
読んだ
大雑把な要約

Rustは非常に素晴らしく成功しているがまだ改善できる点がある
一方で既存の言語には問題があり
他の新言語は社会的賛同が低い
そのため唯一の解決方法はRustをさらに改善に導くことである
そこで具体的な改善案を示す
192デフォルトの名無しさん
垢版 |
2024/08/14(水) 00:30:36.57ID:gAudIBvM
>>187
> >>182の構造体の例も、「普通は」struct Foo<'a, 'b>(&'a str, &'b str);のように分けるべきだろう
> 例外は実際に.0も.1も同じStringの部分列であることを想定しているような、同じlifetimeを持つ参照から作ることが想定されている場合か、他にもあるかもしれんが
これについていえば、必要なのは「構造体は自身よりも寿命の短いデータへの参照を持たない」ことなので、基本的には Foo<'a> だけで済むと思う

fn func() {
 let a: String = "foo".to_owned();
 {
  let b: String = "bar".to_owned();
  {
   let c = Foo{&a, &b};
  }
 }
}

これはaとbは違う寿命を持つけど、Fooとしては「c が生きているうちに a と b の寿命が尽きない」ことが保証されてれば良いだけなので、aとbの寿命の区別までは必要まではない
だからメンバーは Foo<'a> の寿命である &'a だけで済むという感じ
自分の認識違いだったらすまん
2024/08/14(水) 00:42:39.43ID:9bQ7P5cg
働け
194デフォルトの名無しさん
垢版 |
2024/08/14(水) 01:41:38.41ID:trbBeScR
>>153
ライブラリが乱立してる点
2024/08/14(水) 02:10:03.49ID:/eVAI9ia
>>194
まずasync/awaitはRustの標準機能
そして使われるFutureやPollやWakerなど各種はRustのcore標準ライブラリにありno_stdでも使える
asyncランタイムは用途ごとに適したものを自由に作ったり選んだり使える
標準的に使われるものはtokioがダウンロード10倍差で圧勝しており乱立と悩まなくていい
2024/08/14(水) 06:30:03.80ID:xnFjuNS8
>>164
ライフタイム注釈が間違っているのにコンパイルが通ってしまい正しく動作しない、というプログラム例を一つでも示せばよいのではないか?
そんな例は存在しないと思う
コンパイルが通ったらそのプログラムでライフタイム注釈は間違っていなくて正しく動作するよ
2024/08/14(水) 09:24:59.76ID:9a/DnTrG
挙げた手降ろせない子どもすごい増えたよな
折り合いつける知能もない
2024/08/14(水) 09:32:09.98ID:92pG5tQ9
>>183
>trait実装の場合にはtraitの宣言に合わせるために実際より小さいlifetimeを指定しないといけない場合があるしれないが

kwsk
そっちの方が知りたい
199デフォルトの名無しさん
垢版 |
2024/08/14(水) 10:02:37.51ID:jTlv+Rl8
Tokioが圧勝しているせいでtokio でしか使えないライブラリが出てきていて、tokioが終わったらRustのasyncもオワる状況
2024/08/14(水) 10:09:25.77ID:LfKGsMNu
tokio以外を考慮してるライブラリの方が少数派だろ
2024/08/14(水) 10:09:58.25ID:OuxAzukv
>>199
GAFMなどIT大手がtokio直接支持してるから不滅だよん
202デフォルトの名無しさん
垢版 |
2024/08/14(水) 10:12:07.88ID:jTlv+Rl8
>>201
GAFMは飽きっぽいから、支援されてるから不滅ってことはない
あ、Rustの寿命がそれより先に来るってことかな?
2024/08/14(水) 10:21:35.01ID:OuxAzukv
Rustに代わりうるプログラミング言語の芽すらないよん
IT大手各社が結束して同じ新言語を支持したのはRustが最初で最後かな~
2024/08/14(水) 10:31:23.23ID:LfKGsMNu
>>203
zig 1.0待ちだろ
2024/08/14(水) 10:45:06.96ID:OuxAzukv
Rustより保証してくれることが減ってしまう言語は代わりにならないよん
2024/08/14(水) 10:50:42.29ID:z6gmNdON
良いか悪いかよりも一貫しているかどうかが寿命を決める。
(いや、及第点程度には良いものである必要はあると思うけど。)
C がよい言語とはとても言えないけど皆が一貫して使ってたから価値あるものとして認められてたわけで。
プログラミング言語も言語だからね。
2024/08/14(水) 10:51:49.79ID:VXEGQejB
>>192
lifetimeって究極的には関数の引数と戻り値の間でのリソースの依存関係を示すものだから
FooのメソッドもFooを引数に取る関数も無いなら、どっちでもいいよとしかならんと思う
そして実際にメソッドや関数を定義するときになったら、別々になっていたほうが一番柔軟に指定できるから、第一選択としては分けておいたほうがいい、というのが俺の直感

>>196
「コンパイルが通ってしまい正しく動作しない、というプログラム」が作れてしまうことを問題にしているのではない
「コンパイルは通って正しく動作もするが、制約が強すぎるために使える状況が過剰に限定される関数」が作れてしまうということを問題にしている
だから前者の例を出すことに意味は無いし、あなたもよく知っている通りそれは実際できないだろう
そして「Rustのライフタイムに関するよくある誤解」5)のnext()は後者の問題を示す例になっている

>>196
trait Iteratorではそうなっていないけど、例のnext()が別のtraitのメソッドfn next<'a>(&'a self) -> Option<&'a u8>として宣言されていれば、実装側はそれに従わざるを得ない、と思ったけど
現実にそんなことが要求されることがあるか、と聞かれるとちょっと微妙かも
2024/08/14(水) 12:46:04.27ID:9gU6ei1I
zigは本当に最強
2024/08/14(水) 13:07:18.24ID:HK0RNQ5P
>>174
>> next()を1回呼ぼうが2回呼ぼうが、

Rustではその1回目のnext()の値を保持したまま2回目を使えるかどうかの違いが大きいんだよ

最初の仕様は各々の値が使えれることが確認できればいい仕様になっているね
assert_eq!(Some(&b'1'), bytes.next());
assert_eq!(None, bytes.next());

その次の仕様は1回目の値を持ったまま2回目の値を使える仕様になっているね
let byte_1 = bytes.next();
let byte_2 = bytes.next();
if byte_1 == byte_2 {
// 何かしらの処理
}

つまり拡大された別の仕様となっているわけだよ
そのため最初の仕様ではライフタイムがselfと同じでも全く問題ないけど
次の仕様ではselfと異なるものでないといけなくなる
そこが本質があってどちらの仕様も別のものとして存在しているわけよ

わかりやすく例を挙げて説明すると
次のように&[T]ではなくVec<T>を保持するバージョンに変えてみよう
2024/08/14(水) 13:08:44.09ID:HK0RNQ5P
>>209の続き
相違点以外は元コードと同じ名前と形で書いています

struct ByteIter {
vec: Vec<u8>,
index: usize,
}

impl ByteIter {
fn next(&mut self) -> Option<&u8> {
if self.index >= self.vec.len() {
None
} else {
let byte = &self.vec[self.index];
self.index += 1;
Some(byte)
}
}
}

参照を持たなくなったので一見すると有利になっているように見えるけど
最初の仕様しか満たせなくなってる!
ところがこれはGATを用いたLending Iteratorでは当たり前の仕様なんだよ
つまり制限された間違った仕様というよりは別の仕様と捉えることもできるんじゃないかな
2024/08/14(水) 19:39:11.70ID:VXEGQejB
>>209-210
「ByteIterはバイトのスライスを繰り返すイテレータです」
「今こうして前のプログラムを見返してみると、明らかに間違っていましたね」

とかの日本語で説明した文章をすべて無視して、コンパイルの通ったRustコードだけを正しいものとして「プログラマの意図」を見出そうとすると、そうなるんだね
どう考えるとそういう結論に至るのかはよく分かった
2024/08/14(水) 19:54:05.99ID:HK0RNQ5P
>>211
最初のプログラムのどこが間違っていると主張していますか?
assertも通って機能していますよ
2024/08/14(水) 20:38:24.97ID:VXEGQejB
日本語を読めば分かるよ
新ネタ無さそうならもう終わりにしとくね

lending iteratorって発想は無かったから、そこだけは面白かったかもね
2024/08/14(水) 20:56:34.44ID:HK0RNQ5P
>>213
最初のプログラムでイテレータの仕様を満たしており正しく動作していま
すよ
コードに問題はありません

その後に行われようとしているのはイテレータの仕様とは別の
以下を通す新たな仕様を満たすための拡張です

| let byte_1 = bytes.next();
|         ----- first mutable borrow occurs here
| let byte_2 = bytes.next();
|         ^^^^^ second mutable borrow occurs here
| if byte_1 == byte_2 {
|   ------ first borrow later used here

両者を区別できていますか?
2024/08/14(水) 23:52:37.44ID:glbfXLC+
最初のプログラムは関連型のItemを定義できないからIteratorの仕様を満たせないだろ
type Item = &'a u8;
だと拡張(というか修正)した方のコードになる
「正しい」の意味が噛み合ってないことに気付こう
2024/08/14(水) 23:59:55.13ID:orQgr5Ob
結局>>157の記事は破綻してるんだな
イテレータをstd::iter::Iteratorだけを指す狭い意味に取ると
その記事の最初の&u8をItemにした時点でコンパイルエラーとなるから記事は破綻
イテレータを広い意味にとってその記事の方法でもOKとすると
最初の実装でイテレータとしての機能は満たしていて
それを&'a u8にする必要はなくなり記事は破綻
2024/08/15(木) 00:04:11.15ID:JUA9dFMu
「コンパイルが通ったならばライフタイムの記述は正しい」で常に合ってるのに、
それがいかにも間違ってるかのように仕立て装うから矛盾が出てる感じ。
2024/08/15(木) 13:27:43.17ID:TpdjcC5K
Rustは良い言語だと思うけど
清書用だね
2024/08/15(木) 14:13:48.29ID:ntgKOifP
他の言語よりバージョン管理(git)の技術が問われる気がする
全体の整合性が求められるから個人開発でもまじめにブランチ使わないとしんどい
2024/08/15(木) 14:43:48.85ID:vVr6IPen
>>219
整合性が求められるのはどの言語でも同じ
どの言語でもgitを使うかどうかブランチを切るかどうかの方針や判断は同じようになされて言語とは関係ない
■ このスレッドは過去ログ倉庫に格納されています
5ちゃんねるの広告が気に入らない場合は、こちらをクリックしてください。