錆ずブラバの逆説

数週間前、Andrei AlexandrescuによるRust、D and Goの比范分析に出䌚いたした。 C ++コミュニティの尊敬されおいるメンバヌで あり、Dプログラミング蚀語の䞻任開発者である Andreiは、ストヌリヌの最埌でRustに衝撃的な打撃を䞎え、かなり掞察力に富んだ芳察のように芋える䜕かを述べたした。



Rustコヌドを読むず、「友人は友人に足の1日を逃しおはいけない」ずいうゞョヌクを思い起こさせ、andせた脚でバランスの取れた胎䜓の男性の挫画画像を連想させたす。 Rustは、安党性ずゞュ゚リヌの取り扱いを最重芁芖しおいたす。 実際、これはめったに本圓の問題ではなく、このアプロヌチはコヌドを考えお曞くプロセスを単調で退屈なプロセスにしたす。



アンドレむずのいく぀かの䌚議の埌、圌のスピヌチのいく぀かを芋お、私は圌がからかうのが奜きだず確信したした。 しかし、逌を飲み蟌みたしょう。 このゞョヌクはおもしろそうに芋えるずいう理由だけで面癜いのでしょうか、それずもゞョヌクの䞀郚しか持っおいないからでしょうか



ブラバの逆説


毎回、プログラミング蚀語の特定の機胜の利点を反映しお、Paul Graham の゚ッセむ「埁服する平凡さ」に戻りたす。 プログラマヌの間の興味深い珟象に぀いお語っおおり、圌はこれを「パラドックスオブブラバ」ず呌んでいたす。 知らない人にずっおは、逆説は次のように聞こえたす。ある蚀語のBlabを䜿甚するプログラマがあるずしたしょう。 衚珟力の面では、Blabはすべおのプログラミング蚀語の抜象性の連続の真ん䞭にありたす。 これは最も原始的ではありたせんが、最も匷力なプログラミング蚀語ではありたせん。



私たちのBlubプログラマヌは、プログラミング蚀語の「䞋䜍」郚分を芋るず、これらの蚀語が自分の愛するBlubよりも衚珟力に乏しいこずを簡単に芳察したす。 しかし、私たちの仮想プログラマヌがスペクトルの「䞊䜍」郚分を芋るずき、圌は通垞圌が実際に芋䞊げおいるこずに気づきたせん。 ポヌルは次のように説明しおいたす。



圌が芋おいるのは「奇劙な」蚀語だけです。 おそらく、圌はそれらをBlabaず同等であるず認識しおいたすが、それらの䞭にはただ無意味で理解できないゎミがたくさんありたす。 プログラマヌにずっおは十分です。圌自身がBlabaに぀いお考えおいるからです。



私がこれを最初に読んだずき、私は思いたした「うわヌ、これは非垞に掞察力に富んでいたす。」 䜕幎埌にこの抂念が私の考え方に根付くず誰が考えたでしょう。私が人々にプログラミングの方法を教えようず詊みたずき。



Microsoftの蚀語のプロゞェクトマネヌゞャヌずしお、Javascriptの型付きバヌゞョンであるTypeScriptに取り組んでいたす。 間違いなく、䞻にJavaScript開発者の聎衆ず話をし、Javascriptに少し匷い型付けを远加するのがどれほど玠晎らしいかずいう考えを䌝えようずするず、悲芳的な顔が私を芋たす。 毎回。 オプションであっおも。 半ダヌスのメリットを説明した埌でも。 ポヌルが蚀ったように、それはただ「奇劙」に芋えたす。 JavaScriptプログラマヌにずっお、TypeScriptは基本的にJavaScriptず同じように芋えたすが、バカげたものやあいたいなものがたくさんありたす。



他のプログラミング蚀語のチヌムず話をしたり、䌚議でたすたす倚くの人を芋るず、ポヌルの芳察は正確であるだけでなく、驚くほど普遍的であるこずがわかりたした。 ほずんどのプログラマヌは、䞀床も䜿甚したこずのない新しいプログラミング蚀語を目にするず、あらゆる力で抵抗する準備ができおいたす。 新しい、それらに異質な機胜は、それらにアレルギヌ反応を匕き起こしたす。 長い間新機胜を䜿甚しお初めお、圌らはこれがすべお無駄な迷走ではないこずを理解し始めたす。



芁するに、Blobのパラドックスは、プログラマヌずしお私たちが考慮しなければならないもの、私たちが陥りがちなもの、そしお私たちがすべおの努力で脱出すべきものです。



やっおみたしょう。 Rustの最も奇劙で最も圹に立たない機胜をいく぀か芋おみたしょう。 そしお、非ブロック化を実行できるかどうかを確認したす。



奇劙なでたらめ番号1。 さび倚型


Rustで、2぀の異なる構造を印刷するために少しのポリモヌフィズムを䜿甚するプログラムを䜜成したしょう。 最初にコヌドを瀺し、次に詳现に調べたす。



use std::fmt; struct Foo { x: i32 } impl fmt::Display for Foo { fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { write!(f, "(x: {})", self.x) } } struct Bar { x: i32, y: i32 } impl fmt::Display for Bar { fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { write!(f, "(x: {}, y: {})", self.x, self.y) } } fn print_me<T: fmt::Display>(obj : T) { println!("Value: {}", obj); } fn main() { let foo = Foo {x: 7}; let bar = Bar {x: 5, y: 10}; print_me(foo); print_me(bar); } 

それはなんずvyrviglaznoeです はい、ポリモヌフィズムがありたすが、これはOOPによく䌌おいたせん。 このコヌドでは、䞀般化だけでなく䞀般化も䜿甚したすが、このアプロヌチには倚くの制限がありたす。 そしお、このimplは䜕ですか



郚分に行きたしょう。 倀を保存するために2぀の構造を䜜成したす。 それらに実装する次のステップは、 fmt::Displayず呌ばれるものです。 C ++では、 ostream <<挔算子をオヌバヌロヌドしたす。 結果は同様です。 これで、print関数を呌び出しお、構造䜓を盎接枡すこずができたす。



これはすでに話の半分です。



次に、 print_me関数を取埗したす。 この関数は䞀般化されおおり、 fmt::Displayできる堎合は䜕でも䜿甚できたす。 幞いなこずに、私たちは構造がそれを行えるこずを確認したした。



他のすべおは簡単です。 構造䜓のいく぀かのむンスタンスを䜜成し、それらをprint_me印刷するために送信したす。



ふう...䞀生懞呜働かなければなりたせんでした。 これが、Rustでポリモヌフィズムが行われる方法です。 党䜓のポむントは䞀般化です。



では、C ++に少し切り替えたしょう。 倚くの、特に初心者は、テンプレヌトの䜿甚をすぐに考えるこずができず、オブゞェクト指向のポリモヌフィズムの道をたどりたす。



 #include <iostream> class Foo { public: int x; virtual void print(); }; class Bar: public Foo { public: int y; virtual void print(); }; void Foo::print() { std::cout << "x: " << this->x << '\n'; } void Bar::print() { std::cout << "x: " << this->x << " y: " << this->y << '\n'; } void print(Foo foo) { foo.print(); } void print2(Foo &foo) { foo.print(); } void print3(Foo *foo) { foo->print(); } int main() { Bar bar; bar.x = 5; bar.y = 10; print(bar); print2(bar); print3(&bar); } 

かなり簡単ですね。 さお、ここに小さなクむズがありたすC ++コヌドは正確に䜕を印刷したすか



掚枬しおいなければ、萜胆しないでください。 あなたは良い仲間です。



ご想像のずおり-おめでずうございたす ここで、正しい答えを出すためにC ++に぀いおどれだけ知っおおくべきかを少し考えおください。 私が芋るものから、スタックがどのように機胜するか、オブゞェクトがコピヌされるずきにどのようにコピヌされるか、ポむンタヌがどのように機胜するか、リンクがどのように機胜するか、仮想テヌブルがどのように配眮されるか、そしお動的ディスパッチずは䜕かを理解する必芁がありたす。 OOPスタむルでいく぀かの簡単な行を曞くだけです。



私がC ++を孊び始めたずき、この登山は私にずっおあたりにも急であるこずが刀明したした。 幞いなこずに、私のいずこはC ++の専門家であるこずが刀明し、私を圌の翌の䞋に連れお行っお、圌は私に打たれた道を芋せおくれたした。 それにもかかわらず、私はこの䟋のように、なんずか子どもたちの過ちを犯したした。 なんで C ++の難攻䞍萜の理由の1぀は、その開発における高い認知負荷です。



認知的負荷の䞀郚は、本質的にプログラミングに固有のものに圓おはたりたす。 スタックを理解する必芁がありたす。 ポむンタヌがどのように機胜するかを知る必芁がありたす。 しかし、C ++は負荷の床合いを増加させるため、倀が完党にコピヌされない堎合、仮想ディスパッチが䜿甚される堎合、および䜿甚されない堎合を理解する必芁がありたす-開発者が䜕かを行う堎合、コンパむラからの譊告なしで、 「ほずんどの堎合、悪い考えです。」



これは、C ++ずの戊争を詊みるものではありたせん。 Rustの倚くのものは、C ++から取られた䜎レベルで効率的な抜象化の哲孊を維持するずいう考え方で実装されおいたす。 Rustの䟋ず非垞によく䌌たコヌドを曞くこずもできたす。



Rustが実際に行うのは、継承をポリモヌフィズムずは別にするこずです。これにより、最初から䞀般化を䜜成する方向に考えさせるこずができたす。 したがっお、あなたは䞀般的な甚語で最初の日から考え始めたす。 ただし、継承を倚態性から分離するこずは、特にそれらを垞に䞀緒に䜿甚するこずに慣れおいる堎合は特に、奇劙なアむデアのように思えるかもしれたせん。



このような分離は、Blub効果の最初の兆候の1぀を匕き起こす可胜性がありたす。継承ずポリモヌフィズムを分離する䞀般的な利点は䜕ですか ずころで、Rustには継承がありたすか



信じられないかもしれたせんが、少なくずもRust 1.6では、構造を継承するための特別なツヌルはたったくありたせん。 代わりに、蚀語の特別な抂念である「タむプ」の助けを借りお、それらの機胜が構造自䜓の境界を超えお拡匵されおいたす。 特性を䜿甚するず、メ゜ッドを远加したり、メ゜ッドの実装を芁求したり、既存のシステムのあらゆる方法でデヌタ構造を改良したりできたす。 トレむトは継承もサポヌトしたす。あるトレむトは別のトレむトを拡匵できたす。



深く掘り䞋げるず、䜕か他のものに気付くこずができたす。 Rustには、C ++で心配する必芁のあった問題がすべおありたせん。 関数が䜕らかの方法で呌び出されたずきに䜕かが倱われるこずや、仮想ディスパッチがコヌドに䞎える圱響に぀いお考えるこずはできなくなりたした。 Rustでは、タむプに関係なく、すべおが同じスタむルで機胜したす。 したがっお、幌幎期の間違いの党䜓のクラスは単に消えたす。



レヌンに泚意しおください。-タむプに関する詳现は、本「ロシア語プログラミング蚀語」のロシア語の翻蚳にありたす。



奇劙なでたらめ数2。 ぀たり、䟋倖はありたせんか


Rustにはないものに぀いお話しおいるので、次の奇劙なこずは䟋倖がないこずです。 それは䞀歩埌退ではありたせんか 私たちは間違いをどうしたすか 䞀床にすべおを凊理するためにそれらをプッシュできたすか



さお、モナドを知る時間です。



でも...倧䞈倫、冗談ですが、今回はそれらなしでもできたす。 Rustでは、゚ラヌ凊理ははるかに簡単です。 実際にどのように芋えるかの䟋を次に瀺したす。 たず、関数宣蚀がどのように芋えるかの䟋



 impl SystemTime { ///     pub fn now() -> SystemTime; ///  ,   ""   pub fn duration_from_earlier(&self, earlier: SystemTime) -> Result<Duration, SystemTimeError>; } 

now関数は単にSystemTimeを返すだけで䟋倖はありたせんが、 duration_from_earlierはDurationたたはSystemTimeErrorのいずれかの倀を取るこずができるRsult型を返すこずに泚意しおください。 したがっお、関数のすべおの可胜な結果成功ず倱敗の䞡方がすぐにわかりたす。



しかし、これらの䟋倖的な状況はすべお、戻り倀に混乱をもたらしたす。 誰がコヌドでこれを芋たいですか もちろん、垞に゚ラヌをチェックするのは玠晎らしいこずですが、䟋倖のポむントは、゚ラヌをロヌカルで凊理できるだけでなく、トップに転送しお1か所で凊理できるこずです。



Rustでも同じこずができたす。



 fn load_header(file: &mut File) -> Result<Header, io::Error> { Ok(Header { header_block: try!(file.read_u32()) }) } fn load_metadata(file: &mut File) -> Result<Metadata, io::Error> { Ok(Metadata { metadata_block: try!(file.read_u32()) }) } fn load_audio(file: &mut File) -> Result<Audio, io::Error> { let header = try!(load_header(file)); let metadata = try!(load_metadata(file)); Ok(Audio { header: header, metadata: metadata }) } 

完党には明らかではありたせんが、このコヌドはスロヌ䟋倖を䜿甚したす。 try!マクロのチップ党䜓try! 。 圌はかなり簡単なこずをしおいたす。 関数を呌び出したす。 成功した堎合、圌は蚈算結果をあなたに枡したす。 代わりに゚ラヌが発生した堎合は、 try! この゚ラヌがスロヌされ、珟圚の関数の実行が完了したす。



぀たり、 load_header呌び出すずきにfile.read_u32に問題がある堎合、関数はio::Errorを返したす。 さらに、 load_audio同じこずが起こり、同じ゚ラヌが返されたす。 呌び出し元の関数が最終的に゚ラヌを凊理するたで続きたす。



レヌンに泚意しおください。- ゚ラヌ凊理の詳现に぀いおは、Habrの蚘事「Rustでの゚ラヌ凊理」を参照しおください。



奇劙なでたらめの数3。 借甚チェッカヌ


これは面癜いです。 Rustに぀いお話すずきに倚くの人が最初に蚀及するのは、借入チェッカヌです。 さらに、他のプログラミング蚀語ず区別しお、Rustの䞻な機胜ずしお提瀺されるこずがよくありたす。 たずえば、Andreyの堎合、ボロヌチェッカヌは「ハルク型の胎䜓」Rustです。 私にずっお、ボロヌチェッカヌは単なるコンパむラヌチェックです。 タむプチェックず同様に、ボロヌチェッカヌを䜿甚するず、実行時に発生する前にほずんどのバグをキャッチできたす。 以䞊です。 もちろん、最初は怪物のような仕掛けのように思えるかもしれたせんが、ここでのポむントは、Rustがいく぀かの新しい䞍可解な型システムを孊ぶこずを匷制するずいうこずではなく、それず連携する胜力があなたのために新しい筋肉を構築するずいうこずですプログラマヌ。



それでは、ボロヌチェッカヌはどのような゚ラヌをキャッチしたすか



メモリを解攟した埌のポむンタヌの䜿甚


そうそう、叀兞的な状況では、最初にメモリを解攟しおから再び䜿甚したす。 ほずんどの堎合、これがたさに恐ろしい「ヌルポむンタ䟋倖」でプログラムがクラッシュする理由です。



解攟埌䜿甚を回避するC ++の「グッドプラクティス」が倚数ありたす。RAIIを䜿甚する、生のポむンタヌの代わりに参照たたはスマヌトポむンタヌを䜿甚する、APIの所有暩ず関係を借甚するなどです。 Andreiによれば、それはすべお「コヌドを考えお曞くプロセスを単調で退屈なプロセスに倉えおしたう」ずいうこずです。 よく蚓緎されたC ++プログラマヌのチヌムは、単調で退屈な䜜業を行うこずで、ほずんどの解攟埌䜿甚ミスを回避できたす。これは、すべおの「グッドプラクティス」に埓い、高床な資栌を持぀C ++専門家のみでチヌムをcheし、補充するこずはないためです。



無効なむテレヌタヌ


C ++で繰り返し凊理したコンテナを倉曎しなければならず、これにより将来突然クラッシュするこずがありたすか しなければなりたせんでした。 コンテナに少なくずも1぀の芁玠を远加たたは削陀した堎合は、コンテナを実装しおむテレヌタを無効にする必芁がありたす。



私はこのレヌキを螏むこずはあたりありたせんが、それでも時々発生したす。



デヌタの競合状態


Rustでは、デヌタは共有たたは可倉です。 デヌタを倉曎できる堎合、耇数のストリヌム間でデヌタを共有できないため、2぀のストリヌムで同時に倉曎を開始する方法はなく、競合状態が発生したす。 デヌタが共有されおいる堎合、デヌタは倉曎できないため、任意の数のストリヌムから奜きなだけ読み取るこずができたす。



C ++たたは他の倚くの優れた䞊列ラむブラリを備えた蚀語の䞖界から来た堎合、そのような制限は厳しすぎるかもしれたせん。 幞いなこずに、これはストヌリヌ党䜓ではありたせんが、より耇雑な抜象化を䜜成するための䞀連の単玔なルヌルを提䟛する基盀です。 物語の残りの郚分は珟圚曞かれおいたす。 Rust゚コシステムでは 、 同時実行指向のラむブラリが増えおいたす。 もっず孊ぶこずに興味があるなら、圌らの仕事の原理を孊ぶこずができたす。



所有暩の远跡


この抂念は少し冗長に芋えるかもしれたせんが、実際、これはたさにC ++が垞に戊っおいるものです。 前に、「APIの所有暩ず借甚関係を文曞化する」ための優れたプラクティスの1぀に蚀及したした。 問題は、この情報がコヌドに盎接含たれるものではなく、コメントに保存されるこずです。



スクリプトは次のずおりです。C++で蚘述し、他の誰かが蚘述したラむブラリを呌び出す必芁がありたす。 これがCラむブラリであり、匕数ずしお生のポむンタを取るずしたしょう。 このラむブラリに転送したものを埌で削陀するように泚意する必芁がありたすか たたは、圌女は自分の構造の1぀にデヌタを保存するこずでこの責任を負いたすか Rubyのようなスクリプト゚ンゞンを呌び出しおいるのでしょうか その埌、誰がデヌタを所有したすか



ドキュメントを読む代わりに、Rustではボロヌチェッカヌを䜿甚しおラむブラリAPIの正しい䜿甚を垞にチェックするこずにより、期埅を確実にするこずができたす。



その他


借甚チェッカヌは、他の倚くの゚ラヌを回避するのに圹立ちたす。 たずえば、䜜成した関数で受け入れる可倉デヌタは倖郚状態に圱響を䞎えないずいう事実に垞に䟝存するこずができ、適切ず思われる堎合は安党に倉曎できたす。



ちなみに、これは、Cのようなプログラミング蚀語では困難な远加の最適化の幅広い機䌚を切り開きたす。コンパむラヌは、耇数の゚むリアスを持぀倀は倉曎可胜ではないこずを保蚌しおいるため、その逆も同様です-倉曎可胜な倀には垞に1぀の名前しかありたせん。



レヌンに泚意しおください。所有暩ず借甚の抂念の詳现に぀いおは、本「ロシア語プログラミング蚀語」のロシア語の翻蚳を参照しおください。



でたらめな数4。 それらを砎るにはルヌルが必芁です


Rustの匷みの1぀はプラグマティズムだず思いたす。 厳密な制限のほずんどは、 unsafeやmem::transmuteなどの機胜で回避できたす。 ボロヌチェッカヌはあなたの問題を解決するのに適しおいたせんか 問題ありたせん、ただ抜いおください。



レヌンに泚意しおください。-厳密に蚀えば、これは真実ではありたせん。Rustでは、借甚チェッカヌを無効にする簡単な方法はありたせん。安党でないブロック内でも、フルキャパシティで動䜜したす。 unsafeでunsafeブロックの堎合ず同様に、 生の *const Tおよび*mut Tポむンタヌを䜿甚するこずもできたす。これらは、Cからのポむンタヌずほが同様に機胜したす。それらの䜿甚は、借甚芏則によっお制限されたせん。詳现に぀いおは、 「The RustonomiconThe Dark高床で安党でないRustプログラミングの技術 。 」



これにより、Cラむクなシステムプログラミング蚀語で慣れおいるこずなら䜕でもできたす。 Rustの利点は、最初からデフォルトで安党なコヌドを蚘述し、必芁に応じお安党でないセクションを远加する方がはるかに簡単であるこずです。 最初は安党でないものに基づいお安党なコヌドを曞くこずははるかに困難です。



Rustには遞択肢がありたすが、自分で足を撃たないように勧めおいたす。



では、足の調子はどうですか


圌の足元に戻っお、Rustは圌のトレヌニングを逃したしたか 圌は䞀方的になりたしたか 圌は間違ったこずに集䞭しおいたしたか



錆は毎日匷くなっおおり、幞いなこずに、圌は背䞭を曲げずにスクワットを行う方法をよく知っおいたす。 この点を過倧評䟡するこずは困難です。 Rustの哲孊には匷固な基盀があり、それは蚀語が成長し発展するこずを意味したす。



翻蚳者からい぀ものように、私はロシア語を話すRustコミュニティの翻蚳ず貎重なコメントの助けに感謝しおいたす。

Source: https://habr.com/ru/post/J275801/


All Articles