Rust part8
■ このスレッドは過去ログ倉庫に格納されています
>>220 例えば、Cだと、 int *func() { int a; return &a; } としても警告は出る可能性はあってもコンパイルエラーにはなりませんが、 Rustでは同等のことはできないようになっていますね。 でも、Heapから確保したオブジェクトは、それを参照している変数の個数は 動的に変わります。というのは、Heapとは解放のタイミングを後に遅らせる ための仕組みな分けですから。 オブジェクトを参照している変数の個数は静的に決まらないと言うことは、 参照カウンタやGarbageCollectionのような動的な検査の仕組みが全く無ければ 解放できないはずですね。 もし静的に解放タイミングを決められる可能性があるとすれば、参照の個数に上限を 設けることによってです。簡単な例ではそのオブジェクトを参照する変数が1つしか ないなら、その変数にnullや別の値が代入されたり、その変数が削除されるタイミング だけを静的に調べれば自動解放できるはずです。 しかし、2つ以上になった場合、参照カウンタ無しで静的に削除タイミングを決定するのは かなり難しくなります。 私が読んだ説明記事では所有権やライフタイムはローカル変数かつシングルスレッドの例しかなかったんですが、 それだとRustがどう非同期処理に強いのかイメージできませんでした。 グローバル変数やマルチスレッドでライフタイム等の概念が活用されている例ないですか? Rust では所有権が移るから、資源を共有しないからだろ 資源を共有すると、アクセスするタイミングで、バグってしまう。 A を更新 → B を更新 → Aを読み取り 間に、Bが入ったことで、AはBの値を読み取ってしまったけど、 その場ではエラーにならず、かなり後になって、データが何かおかしいと誰かが気づくかも知れない。 気づかないかも知れない だから、あちこちで排他制御をせざるを得ない。 そうすると、デッドロック・タイムアウトも考えないといけない 資源を共有しないんじゃなくて、共有していい資源かどうかを型(SendとSync)で区別している。 なので排他制御が必要な箇所に入ってなかったらコンパイルエラーにできる。 だからデッドロックなんかは起きるけど、レーシングは起きないって感じ。 ライフタイムはそれほど関係ない。 マルチスレッドの話は置いておいて、そもそも Rustでは、Heapから確保したオブジェクトの解放は自動化されてますか? >>223 修正 >A を更新 → B を更新 → Aを読み取り スレッドA が共有資源を更新 → B が更新 → Aが読み取り 基本的なことが理解出来てない人はまずThe Bookを読もう 次からテンプレに書いといたほうが良さそう 読んでそこまでたどりつくのは大変過ぎますので、どなたか分かる方が >>225 に答えていただければ幸いです。 >>225 自動で解放される。なぜされるのかが気になるならいろいろ想像してないでthe bookを読んだ方がいいと思う。 Rc<T> は参照カウンタ方式で自動解放されますが、循環参照があった場合には自動解放されず、メモリーリークするそうです。 つまり、RustはHeapのメモリーを完全自動では解放できないのです。 C#やJavaは遅いですが、循環参照がある場合でも、GarbageCollectionにより完全自動で解放されます。 つまり、「Rustは、GarbageCollectionがなくてもメモリ解放が自動化されている」 というのは嘘です。 >>228 その部分だけの説明は出来ない。 そこにたどり着くまでの説明は関連してるから。 体系立てて説明している文書が公式にあるのにそれより上手い説明が出来るわけないだろ。 >>231 https://stackoverflow.com/questions/55553048/is-it-possible-to-cause-a-memory-leak-in-rust [Q] Is it possible to cause a memory leak in Rust? Is there any way of causing a memory leak in Rust? I know that even in garbage-collected languages like JavaScript there are edge-cases where memory will be leaked, are there any such cases in Rust? [A1] Yes, leaking memory in Rust is as easy as calling the std::mem::forget function. You can also leak memory if you create a cycle of shared references: A cycle between Rc pointers will never be deallocated. For this reason, Weak is used to break cycles. For example, a tree could have strong Rc pointers from parent nodes to children, and Weak pointers from children back to their parents. >>233 結論的に言えば、Heapから確保されたオブジェクトのメモリの解放は自動化されてません。 なんだ。真面目に聞いてるのかと思ったのに。 まぁご自由にどうぞ。 真面目に回答した相手が荒らしだった時って「何だよ荒らしかよ」みたいな反応より「クッソ抜けるwww」からの「すいません誤爆しました」みたいなレスで「真面目に回答したわけじゃないぞ、シコりながら適当に回答したんだぞ」的な雰囲気を出してったほうが良さそうじゃない? Rc<T> だけを使って循環リストを作った場合、循環参照が生じます。 その場合、その循環リスト全体が誰からも参照されなくなって、つまり、不要に なっても「循環参照問題」が起きるために自動解放できないのです。 C#やJavaでは、この循環参照問題を解決することを主目的として、 遅さを犠牲に Garbage Collection を使っています。 これは「動的解析」に分類されます。 一方、Rustの場合は静的解析だけで済まそうとしていますが、結局、循環参照問題のために 完全自動化は出来ず、解放べきメモリが解放されずに残ってしまうという現象がおきえます。 これを防ぐには、人間側が 循環リストの最後と最初を結ぶためには、weak pointer を使う などの注意深いプログラミングをすることで防ぐしかありません。 つまり、人間の注意深さでメモリーリークを防いでいるだけです。 >>239 リークしない保証が欲しいならそういう言語 (処理系) を使えばいいじゃん。 Rust はリークを確実に排除することを保証しないし、 Rust の定義するメモリ安全にはリークの排除が含まれないことは明言されている。 https://doc.rust-lang.org/book/ch15-06-reference-cycles.html GC を使えばメモリリークが起きないってわけでもないし、 「どのような保証を与えるか?」の線引きが違うだけで きちんと学んできちんと使いこなさなきゃなんだって駄目だよ。 >>241 別に駄目とは言ってません。 「静的解析でメモリ管理を自動化した。」 というのは言いすぎだと言うことです。 そりゃ循環参照とかウンコード書けばの話だろ、何か問題有るのか? 荒らしにかまうやつも荒らし 暇なおじいさんが各言語に難癖つけて回ってる Kotlinの次はRust >>242 結局のところライフタイムは人間が書くし、それを元にいくらかのチェックと推論をするだけ。 今まで自動化できてなかった分 (の一部) を新しいやり方で自動化したってだけのことで、 何もかも自動でやってくれるような最強解析能力を持ってるなんて誰も言ってないよ。 ストローマン論法はやめろよな。 もう一度書くけど、 Rust のシステムはメモリリークの排除を目的にしていない。 リソースの開放をプログラム内で完璧にやろうとせずに プロセスの再起動とかの水準で考えてしまえばいいこともあるかもね メモリ以外のリソース回収に関してはGCよりRust(あるいはC++のスマポ)がはるかに優秀なんだよな。 Rustに慣れると他言語でusingとかするのが面倒になってくるし、ついつい付け忘れて困る。 Kotlinスレでnullポインタの方が良いと 荒らしてたのと同一人物だったのか 体制側と違う意見は大事。 自分の頭で考えない人は、効能書きや権威やネットで書かれたことを鵜呑みにしてしまう。 いやファイルポインタ、コネクション、スレッドなどいくらでもあるだろ。 >>250 こんな出てきたばかりの言語に慣れてる人なんているかい。 ファイルハンドルくらいならusingでもいいけど、デバイスコンテキストみたいな微妙に寿命が長いのが困る。 Disposeとか呼び忘れても大抵GCが回収してくれてぱっと見動くけど、 時々回収してくれなくて再現性の低いバグになるとか。 Box<T>のソースを見ていたら、Box::new が次のような1行だけのソースで、 box キーワードを調べてみたら、組み込みの Box::new の実装とだけしか 情報がない。つまり、ソースがあるようで実質的には無い: impl<T> Box<T> { pub fn new(x: T) -> Box<T> { box x } } 同様に Vec<T> のソースを見ていたら、RawVec なるものが出ていて、 RawVecのソースもあったがそこで、core::ptr なるものが使ってあり、 そのソースはないようだ。 Cにとってかわるシステム言語と言いながら、本質的には組み込み関数ばかりで ソースを追っていくことができない。 Cは最初から理解できて、このような闇が存在しないので、高級アセンブラであり、 システム言語であった。 Rustにはその代わりは務まらないのではないか。 巨大学術掲示板群 - アルファ・ラボ ttp://x0000.net 物理学 化学 生物学 数学 天文学 地理地学 IT 電子 工学 言語学 方言 国語 など Rustは概念が整理されてない。 Cは、ポインタという概念さえ理解すれば(そしてそれは、プログラミングに適性が有る人にはそんなに難しいわけではない)、それだけを頼りにあらゆるものが構築できた。 ところが、Rustはいくら学んでも終わりが無いくらい基礎の部分が難しい。 Box<T>が何をやっているかは、自然言語で説明されるばかりで肝心の コードも擬似コードもなかなか見つからない。 Box<T>のデストラクタである所の Drop trait の drop 関数を調べてみると、 次のようになっており、compilerの埋め込み処理というコメントになっている。 恐らく、Box<T>が削除される際にコンパイラが何らかの処理を入れているが、 ソース中には書いてない。 こんな状態で Cの後釜を名乗ってほしくない。 unsafe impl<#[may_dangle] T: ?Sized> Drop for Box<T> { fn drop(&mut self) { // FIXME: Do nothing, drop is currently performed by compiler. } } >>257 それは悔しいけどちょっと理解できる コンパイラーのソースみるとcompiler builtinで見れないやつとかあるし いや、どこにあんねんと Option<Box<T>> で、Some(Box::new<xxx>) とした場合に、どういう状況の時に このメモリが解放されるのか、 その仕組みも分かりにくい。 >>264 誤: Some(Box::new<xxx>) 正: Some(Box<T>::new(値)) Rustでの代入記号は、i32/f32/bool/str などのprimitive型以外は原則的に copy動作 ではなく、move 動作のようなもので、所有権の移動が発生する。 例外は、Copy traitsが実装されている型の場合で、その場合も copy動作になる。 Option<XXX>は、Copy traitsが実装されているらしく、Optionが他動詞で代入記号 を使うと、copy動作になるらしい。 ただし、これは文書で明確には述べられてないのでよくわからない。 根拠は、Optionのソースは以下のようになっていて、#[derive(Copy, ...)]の部分が、 Copy traitsを自動実装する、という意味になるらしいからだ: #[derive(Copy, PartialEq, PartialOrd, Eq, Ord, Debug, Hash)] #[rustc_diagnostic_item = "option_type"] #[stable(feature = "rust1", since = "1.0.0")] pub enum Option<T> { /// No value #[stable(feature = "rust1", since = "1.0.0")] None, /// Some value `T` #[stable(feature = "rust1", since = "1.0.0")] Some(#[stable(feature = "rust1", since = "1.0.0")] T), } >>267 誤:Optionが他動詞で 正:Option型同士で >>263 compiler builtinはコンパイラのソースの中にあるよ コンパイラのソースでcompiler built-inは見たこと無いけど 汽車 汽車 チンポ チンポ シコシコ チンポッポ チンポを出して シコシコ チンポッポ Rustは、コンパイラ時エラーに悩まされる反面、実行時エラーに悩まされるのを減らす などと言われる。 しかし、コンパイル時エラーが出ると言うことは、裏を返せば、書けないアルゴリズムが存在するということだ。 直感的ではない回りくどい書き方が必要となり記述量が多くなる。 他の言語では好きな書き方が出来て、それはどれも正解だが、Rustでは正解が非常に狭くなる。 正解が狭いことがエラーを減らすなどという人がいるが、実際には、Rustは 書けるアルゴリズムが狭い、と言うことなのである。 これは言語設計の問題である。 なお、ここで言っているアルゴリズムは、全体的なものではなく、細かいミクロ的なものである。 通常の言語では、1つの仕事を細かい変数の使い方まで含めれば数万通り以上に書けるだろう。 そして、そのどれもが正解であり、結果が正しくバグも無いのだから、内のどれかが悪い書き方という ことは特にない。 ところが、Rustでは、その大部分の書き方が出来ないのである。 駄目だから敢えてできなくしているのではなく、Rustが設計上、書けないアルゴリズムがあるということに他ならない。 つまり、Rustは書けるアルゴリズムが、本来コンピュータが書けるアルゴリズムの内の、非常に狭いサブセットに限られてしまうということである。 これは、Rustの大きな欠陥である。 >>274 「駄目な書き方だからエラーにしている」 と言うのは間違いで、正しくは、 「Rustコンパイラの静的解析能力では、書き方を非常に限定しないと 正しいことを保障できなかったり、自動化できなかったりするため、 しょうがなく狭い書き方しか出来なくしている」 と言うことに他ならない。 人間には脳内の静的解析で明らかに正しいことが分かる書き方でも、 Rustコンパイラではでは同じことができないため、敢えて 変数束縛、借用、単一参照など、コンパイラでも解析できる程度の 範囲に書き方を限定して無理やり人間のプログラミングの可能性を 狭めているに過ぎない。 みんなドキュメントコメントで日本語も一緒に書くときってどうしてる? こんな連ねた感じでいいのかな? /// Returns a Person object /// Personオブジェクトを返す 例が悪いわ。 そんな糞コメント書くな、になってしまう。 Cからprintln使ってるRustの関数呼ぶとメモリリークしよる(´・ω・`) >>277 Personオブジェクトを返すって -> Person -> Box<dyn Person> どっちのこと? Box<T> は copy ではなく move なんだとさ。 >>267 Option<T> は、Copy。 Box<T> は、Move。 だから、 Option<Box<T>> は、外側が Copy で、中身が Move らしい。 中身がCopyのときだけOptionもCopyになるんじゃないの?? >>285 ソースを示しておくと、以下の公式(?)サイトに、 「because Box isn't Copy」という言葉が書かれている: https://doc.rust-lang.org/nomicon/checked-uninit.html struct のメンバに local 変数を代入した場合、エラーになる? >>291 それは >>267 とちゃんと整合しているな。 Option<T>は、Copy と Cloneの両方が実装されていると言うことで、 ならば、原則論からすればOptionは代入演算子でMove動作「ではなく」Copy動作 だということになる。 >>292 >ならば、原則論からすればOptionは代入演算子でMove動作「ではなく」Copy動作 は? where T: Copyって書いてるやん オレオレ原則論書き散らかして荒らすのやめてくれ c++の代替というけど、rust理解するにはc++でメモリイメージ固めた方が学習速いんじゃねーの? >>292 Optionは型ではない Option<T>は型 区別してくれ ML系列の記法に慣れるとrustがどうしてalgol系列の記法にしたのか納得できなくなる。どんだけカンマ打たせるねん。 少し頑張れば関数の型も推論できると思うんだけど人まかせにしてるのが好きじゃない。せめて、勝手に挿入できるような記法にしとくべきだったと思う インスタンスでフィールドアクセスにカンマ使わない場合どんなのがいいの? 推論は関数で境界作りたかったんでしょ >>282 日本語のコメントはどう書くだけなのか聞きたかったから特に意図はない例だよ >>283 完全な例だからあれだけどBoxではない struct Person { name: String, } impl Person { pub fn new(name: &str) -> Self { Self { name: name.to_string() } } } 型推論の能力的には関数も全部できるけど、ドキュメント目的であえてしてないはず。 TypeScriptは戻り値の型を省略できるけど、書かないと訳が分からなくなるので言語的には省略出来てもコーディングルールで強制してるわ Cみたいに変数宣言含めて型を全部書くのも面倒だけど、行き過ぎた省略もメンテナンス性を損なうと思う C/C++に疲れた人が使って幸せになれるのがRustだと思ってたけど 実態は全然違うってことか C++17に疲れた人なら結構幸せになれるんじゃない。 C89に疲れた人だと厳しそうだが…。 RustのコンパイラソースをCloneしてそれをベースに名前も違うプログラミング言語作るのってライセンス的にどうなの? rustcの構文解析の部分を変えてからそのrustcも全部その言語に変換したいんだけど >>304 そういう考えだから、人を馬鹿にするんだな。 ちょこっといじっただけの癖に全体を自分が造ったみたいな態度をとる。 >>299 あえてやらないなら、せめてコンパイラが推論した結果を教えてくれよと思う この処理のこの部分だけ一旦切り出して別の処理にしてみたい、とかやるときにすげー大変 Haskellでも型表記は省略できるけどしない方が良いねって作法がメジャーなのは知ってるけど、型推論でサポートしてくれるからrustよりストレス少ない ちょいとクロージャを関数として外に出しとこう、とか、ここ切り分けて別パターン用意して比較してみよう、とかやるのが面倒くさい あ、まずクロージャにして型エラーを起こして教えてもらう、とかやればできるんかな… >>306 型を自分で書くのが面倒なときは()とかi32とか適当な型で埋めておいて、エラーメッセージから正しい型を持ってくるというのはありだよ。 (逆に言えば正しいエラーメッセージを出せるということは、関数プロトタイプまでちゃんと型推論できているということでもある) 関数の型を推論するのを捨ててるから Deref coercionだったりFrom/Intoだったり中身を書くときに型を省略できるんじゃないの? owned/shared/mutの違いに加えてlifetimeもあるから それらも含めて推論することになると現実に使えるレベルになるのかどうか怪しい 少なくとも現時点で注力するようなポイントじゃないと思う >>304 ライセンス的にも道義的にもなんの問題もないんで、ぜひ面白言語作ってくれ。 Rust/Cargoの名前やロゴ使うと商標権には引っかかるのでそこだけちゃんと変えてればOK。 >>309 コミットも常に最新を本家からチェリーピックして最新機能とか享受大丈夫? 道理的に叩かれそうじゃない? HACK言語の成り立ちを参考にしたら 答えが見えてくるかもしれないよ WebKitとBlinkみたいな感じでしょ?へーきへーき githubのissueのタグで頭についてるE-easyとかT-compilerみたいな大文字のアルファベットってどういう意味があるの? >>310 ライセンス的には問題ない 道義的にはライセンス踏襲してRustを元にしてるよって書いとけば問題無いと思う この引数に&つけるのって iter.map(|&i| i * 2) これと同等? for i in iter { let i = &i; } 巨大な学術掲示板群 アルファ・ラボ ttp://x0000.net 物理学 化学 生物学 数学 天文学 地理地学 IT 電子 工学 言語学 方言 国語 など >>318 let i = 1; let &i = i; これだとコンパイル通らないよ 記法の問題で、>>318 は、C++ と勘違いしたんでは。 Rustだと let i = 1; let j = &i; が正しいはず。 iter.map(|i| i * 2) と書いた場合、|i| i * 2 の部分は、closure や Lambda expression, lambdas と呼ばれるものなんだろうけど、|&i| と書く形式はなかなか検索では出てこない。 ■ このスレッドは過去ログ倉庫に格納されています
read.cgi ver 07.5.4 2024/05/19 Walang Kapalit ★ | Donguri System Team 5ちゃんねる