Von C zu Rust wechseln?



  • Rustiger schrieb:

    Wenn du fachlich einer der hier gemachten Aussagen widerlegen kannst, dann kannst du das gerne tun.

    Rust ist halt wie HD-Fernsehen von Bluray für mich, einmal geschaut und dann will man kein SD mehr auf DVD kaufen.

    Du kannst hier gerne nach den Mods schreien und mich Mundtod machen und die Trollkeule raus holen, oder was auch immer. Ich bin nur einer der Rust mag, es gibt aber viele von uns und es werden täglich mehr. Du kannst uns nicht alle löschen, Sicherheit ist wichtig!

    Sicherheit ist mir auch wichtig. Deswegen halte ich ausschau nach Diskussionskanälen, die sich besser schützen.



  • Rustiger schrieb:

    Genau in C++ ist Sicherheit abhängig vom Entwickler, das ist immer schlecht und war auch ein guter Grund von C auf C++ zu wechseln.

    Nenne doch mal bitte ein Codeschnipsel, das mit Rust ein neue Niveau an Sicherheit aufweist, damit wir uns überlegen können, wie man das in C++ nachbilden kann. C++ (und gerade in der 11er Version) bietet ja mit const, "smart pointers", auto, explicit, delete, default, usw inzwischen zahlreiche Hilfsmittel.

    C++ hat die Vorteile und Nachteile jedes mächtigen Werkzeugs - richtig angewandt, ist es effizient, elegant und sicher, in den falschen Händen kann man viel Unsinn mit anstellen.

    Rustiger schrieb:

    Willst du noch mehr automatische Sicherheit ist dies ein guter Grund von C++ auf Rust zu wechseln. Rust ist der nächste logische Schritt, den kann man gehen oder auch nicht.

    bei der Phrase "automatische Sicherheit" denke ich als erstes an Prolog. Da überläßt man die Programmierung gleich dem Computer und beschreibt nur die Aufgabenstellung.



  • Ownership und Borrowing nachzubauen bringt nichts, es muss Teil des Sprachkerns sein. Beim Nachbauen hängt es wieder beim Entwickler, ob er dieses Feature nutzt oder nicht und das will man nicht. Das C++ Vektor Beispiel mit dem undefinierten Verhalten würde in Rust er gar nicht kompilieren:

    #include <iostream>
    #include <vector>
    #include <string>
    
    using namespace std;
    
    int main() {
        vector<string> v;
        v.push_back("Hello");
        string& x = v[0];
        v.push_back("world");
        cout << x;
    }
    
    fn main() {
        let mut v = vec![];
        v.push("Hello");
        let x = &v[0];
        v.push("world");
        println!("{}", x);
    }
    
    $ rustc main1.rs
    main1.rs:5:5: 5:6 error: cannot borrow `v` as mutable because it is also borrowed as immutable
    main1.rs:5     v.push("world");
                   ^
    main1.rs:4:14: 4:15 note: previous borrow of `v` occurs here; the immutable borrow prevents subsequent moves or mutable borrows of `v` until the borrow ends
    main1.rs:4     let x = &v[0];
                            ^
    main1.rs:7:2: 7:2 note: previous borrow ends here
    main1.rs:1 fn main() {
    ...
    main1.rs:7 }           ^
    

    Warum es in Rust zu nicht möglich ist Speicherfehler solcher Art zu produzieren kann man nachlesen oder sich bei YouTube anschauen. Das sind Profis für Programmiersprachen, die können das besser erklären als ich. Ich bin nur Anwender und glaube da den Profis von Mozilla einfach mal.



  • Rustiger schrieb:

    Willst du noch mehr automatische Sicherheit ...

    Wenn ich solch einen Mist lese.

    Schon mal dran gedacht das man für manche Sachen sowas gar nicht haben will bzw. gebrauchen kann? Das wäre genauso fatal wie ein GC. Dies muss dann der Programmierer entscheiden wann was gelöscht wird bzw. wie die Sicherheit implementiert wird.

    Du kommst hier mit Argumenten die weder Hand noch Fuß haben und machst gerade so als ob C++ voll von Sicherheitslücken ist.

    Also, bleib bei deinem Rust und verzieh dich ins Mozilla Forum.



  • Cybertec schrieb:

    Rustiger schrieb:

    Willst du noch mehr automatische Sicherheit ...

    Wenn ich solch einen Mist lese.

    Schon mal dran gedacht das man für manche Sachen sowas gar nicht haben will bzw. gebrauchen kann? Das wäre genauso fatal wie ein GC. Dies muss dann der Programmierer entscheiden wann was gelöscht wird bzw. wie die Sicherheit implementiert wird.

    Oh man, du hast ja mal gar keine Ahnung, worum es geht.



  • großbuchstaben schrieb:

    C++ hat die Vorteile und Nachteile jedes mächtigen Werkzeugs - richtig angewandt, ist es effizient, elegant und sicher, in den falschen Händen kann man viel Unsinn mit anstellen.

    Eine Kettensäge ist richtig angewandt effizient, elegant und sicher. In den falschen Händen kann man viel Unsinn damit anstellen. Aber du nimmst zum Arbeiten auch lieber eine mit Kettenbremse, oder?



  • Rustiger schrieb:

    Ownership und Borrowing nachzubauen bringt nichts, es muss Teil des Sprachkerns sein. Beim Nachbauen hängt es wieder beim Entwickler, ob er dieses Feature nutzt oder nicht und das will man nicht.

    Schwachsinn. Ownership muss nicht im Sprachkern sein. Auch bei Gc-Sprachen versuche ich nach Moeglichkeit immer meine Programmstruktur auf je einen einzigen Besitzer zuzuschneiden, weil es die Anzahl moeglicher unberechtigter Zugriffe und damit Fehlerquellen minimiert.

    Rust und C++ haben das gleiche ownership-modell. Nach Moeglichkeit legt man das Objekt direkt an. Braucht man Zugriff, aber keine Kopie, verwendet man eine Referenz.
    Muss man etwas auf dem Heap reservieren, verwendet man fuer dynamische Arrays einen Container (vector oder Vec) und fuer einfache Objekte unique_ptr oder Box.
    Hat man mehrere moegliche Besitzer, nimmt man shared_ptr oder Rc.
    Erst zur Implementierung von diesen grundlegenden Strukturen oder fuer die Verwendung von C-Libraries greift man zu rohen pointern.

    Borrowing schuetzt lediglich vor Fluechtigkeitsfehlern. Normalerweise schaut man in die Referenz und sieht da, dass die pointer von vector beim push_back nicht erhalten bleiben und laesst es dann bleiben.



  • Rustiger schrieb:

    Ich bin nur Anwender und glaube da den Profis von Mozilla einfach mal.

    Warum glaubst du den Profis von Google nicht und verwendest Go?
    Dieses Borrow System funktioniert nur für triviale Fälle, die eh keinen interessieren.



  • Marthog schrieb:

    Borrowing schuetzt lediglich vor Fluechtigkeitsfehlern.

    Ist nicht der Sinn einer (Hoch-)Sprache, vor Flüchtigkeitsfehlern zu schützen?



  • ich warte lieber. Vielleicht wird eine Version von C++2x das haben, was Rust heute hat.



  • TyRoXx schrieb:

    Oh man, du hast ja mal gar keine Ahnung, worum es geht.

    Was willst du denn?



  • großbuchstaben schrieb:

    krümelkacker schrieb:

    großbuchstaben schrieb:

    welche Nische ist denn offen, die eine weitere Sprache wie Rust besetzen kann?

    [...] Rust will ein besserer C++ Ersatz sein, der es wie C++ auf "zero cost abstraction" aber zusätzlich auch auf Speichersicherheit und Threadsicherheit abgesehen hat.

    und wo ist da die offene Nische?

    Meinst du, daß sichere Software mit herkömmlichen Sprachen nicht möglich ist?

    Nein. Natürlich ist es theoretisch möglich ein Programm in einer anderen Sprache zu schreiben, was keinen Mist baut, auch in C++. Und das klappt sogar meistens in der Praxis. So verdiene ich mir immerhin meine Brötchen seit den letzten 8 Jahren. Ich denke aber, Du verwendest den Sicherheitsbegriff etwas anders als ich. Deswegen habe ich den hier jetzt vermieden.

    großbuchstaben schrieb:

    In C++ ist das nicht zuletzt eine Frage des Programmierstils bzw der Fähigkeiten des Entwicklers.

    Ja, das stimmt. C++ bietet 'ne Werkzeugkiste an, die, wenn man sie effektiv einzusetzen weiß, einem dabei hilft, keinen groben Mist zu bauen … meistens. RAII und Templates sind da in der Hinsicht ganz oben dabei unter den wichtigen C++ Patterns/Features, IMHO.

    Bzgl Nische: Rust ist für die Leute, die in erster Linie vom Compiler garantiert haben wollen, dass sie keine Datenrennen und keine „Speicherfehler“ (im Sinne der Speichersicherheit) produziert haben, ohne dabei auf die Performancevorteile zu verzichten, die sie von C++ gewohnt sind und ohne dabei die C-Altlast mitzuschleppen. (Und natürlich gibt es noch diverse andere Unterschiede, die auch nicht alle für Rust sprechen.)

    Cybertec schrieb:

    TyRoXx schrieb:

    Oh man, du hast ja mal gar keine Ahnung, worum es geht.

    Was willst du denn?

    Ich weiß auch nicht so ganz, was Du mit Deinem Kommentar gemeint hast. Das klang irgendwie so, als würdest Du glauben, Rust erkaufte sich ein gewisses Maß an Sicherheit durch einen Overhead zur Laufzeit. Das trifft jedenfalls nur auf das Bounds-Checking zu — nicht auf das, wobei einem der Borrow Checker zur Übersetzungszeit hilft. Rust lässt dich aber, wenn Du nach entsprechenden Profiling rausgefunden hast, dass irgendwo das Bounds-Checking doch was an der Laufzeit ausmacht (eher unwahrscheinlich), dann kannst du das da selektiv per unsafe auch quasi abschalten. Es vergrößert zwar die „Angriffsfläche“, aber dann gibst Du Dir halt Mühe, da keinen Bug einzubauen. Ggf kann man es in so einem Fall auch ohne unsafe lösen. Das Iterieren über ein Array per Iterator (statt Array Indexing je Element) ist sehr flott.



  • Ich lese hier schon eine Weile mit, aber so langsam platzt mir die Hutschnur. Die Rust-Leute klagen, wenn man etwas über Rust sagt, ohne Ahnung zu haben (verständlich), aber tun dann dasselbe bei C++?

    Rustiger schrieb:

    #include <iostream>
    #include <vector>
    #include <string>
    
    using namespace std;
    
    int main() {
        vector<string> v;
        v.push_back("Hello");
        string& x = v[0];
        v.push_back("world");
        cout << x;
    }
    

    Ersetzte vector durch deque und es kompiliert und läuft fehlerfrei. Und dass dieses Beispiel absolut willkürlich ist (man benutzt Referenzen als Variablen so gut wie nie) lasse ich mal aussen vor.

    Als Unwissender: Kann mir jemand erklären, wie ich in Rust println für meine eigenen Structs überladen kann?



  • Du meinst sowas hier? Ich habe das mal aus der Rust-Doku kopiert.

    use std::fmt;
    use std::f64;
    use std::num::Float;
    
    #[derive(Debug)]
    struct Vector2D {
        x: int,
        y: int,
    }
    
    impl fmt::Display for Vector2D {
        fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
            // The `f` value implements the `Writer` trait, which is what the
            // write! macro is expecting. Note that this formatting ignores the
            // various flags provided to format strings.
            write!(f, "({}, {})", self.x, self.y)
        }
    }
    
    // Different traits allow different forms of output of a type. The meaning
    // of this format is to print the magnitude of a vector.
    impl fmt::Binary for Vector2D {
        fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
            let magnitude = (self.x * self.x + self.y * self.y) as f64;
            let magnitude = magnitude.sqrt();
    
            // Respect the formatting flags by using the helper method
            // `pad_integral` on the Formatter object. See the method documentation
            // for details, and the function `pad` can be used to pad strings.
            let decimals = f.precision().unwrap_or(3);
            let string = f64::to_str_exact(magnitude, decimals);
            f.pad_integral(true, "", string.as_slice())
        }
    }
    
    fn main() {
        let myvector = Vector2D { x: 3, y: 4 };
    
        println!("{}", myvector);       // => "(3, 4)"
        println!("{:?}", myvector);     // => "Vector2D {x: 3i, y:4i}"
        println!("{:10.3b}", myvector); // => "     5.000"
    }
    


  • asfdlol schrieb:

    Die Rust-Leute klagen, wenn man etwas über Rust sagt, ohne Ahnung zu haben (verständlich), aber tun dann dasselbe bei C++? [...] Ersetzte vector durch deque und es kompiliert und läuft fehlerfrei. Und dass dieses Beispiel absolut willkürlich ist (man benutzt Referenzen als Variablen so gut wie nie) lasse ich mal aussen vor.

    Ich verstehe nicht, was du meinst.
    Referenzen benutzt "man" ständig.
    Ein Beispiel, warum das gefährlich ist:

    void find_similar_names(std::string const &name, std::vector<std::string> &results);
    

    Ist das hier sicher oder nicht? An der Signatur der Funktion kann ich das jedenfalls nicht erkennen. Und falls es sicher ist, wird möglicherweise innen drin eine unnötige Kopie gemacht.

    std::vector<std::string> names{"a"};
    find_similar_names(names.front(), names);
    

    In Rust würde etwas Äquivalentes nur kompilieren, wenn es sicher ist. Ohne Mehraufwand oder Laufzeitkosten kann leicht zu übersehendes undefiniertes Verhalten ausgeschlossen werden.

    fn find_similar_names(name: &str, results: &mut Vec<String>);
    

    asfdlol schrieb:

    Als Unwissender: Kann mir jemand erklären, wie ich in Rust println für meine eigenen Structs überladen kann?

    Das entsprechende Trait heißt inzwischen std::fmt::Display.



  • TyRoXx schrieb:

    asfdlol schrieb:

    Die Rust-Leute klagen, wenn man etwas über Rust sagt, ohne Ahnung zu haben (verständlich), aber tun dann dasselbe bei C++? [...] Ersetzte vector durch deque und es kompiliert und läuft fehlerfrei. Und dass dieses Beispiel absolut willkürlich ist (man benutzt Referenzen als Variablen so gut wie nie) lasse ich mal aussen vor.

    Ich verstehe nicht, was du meinst.
    Referenzen benutzt "man" ständig.
    Ein Beispiel, warum das gefährlich ist:

    void find_similar_names(std::string const &name, std::vector<std::string> &results);
    

    Ist das hier sicher oder nicht? An der Signatur der Funktion kann ich das jedenfalls nicht erkennen. Und falls es sicher ist, wird möglicherweise innen drin eine unnötige Kopie gemacht.

    Doch, das kann man sehr gut sehen, nämlich daran, dass die Referenz auf den Vektor nicht konstant ist. Das weglassen von const soll immer implizieren, dass du das Objekt verändern möchtest und in dem Moment weißt du, dass das gefährlich ist, Elemente des Vektors ebenfalls als Referenz zu übergeben.

    Edit: Falls du meinen solltest, dass ähnliche Namen in dem Array nicht gesucht werden sollen, sondern die Funktion die sich irgendwie aus den Fingern saugt, dann ist dieser Code noch konstruierter, denn niemand würde ein Result-Vektor erstellen, nur um dort den Namen drin zu speichern (warum auch immer man das tun sollte?), damit er am Ende sowieso mit Ergebnissen überschrieben werden soll.
    Und zu guter Letzt: In C++ benutzt man Rückgabewerte:

    vector<string> find_similar_names(const string&);
    

    Copy-Elision garantiert dir auch, dass da nichts kopiert wird. Alles supi.
    Programmierer, die in C++ solchen Code schreiben würden, würden auch in Rust totalen Mist zusammenschreiben. Bei denen scheitert's dann nämlich an den Grundlagen und da hilft dir keine Programmiersprache weiter.



  • Der Code sollte nicht besonders toll sein, sondern ist auf das Wesentliche reduziert. Wenn du willst, kannst du dir OutputIterator + back_inserter dazudenken.

    Jodocus schrieb:

    Doch, das kann man sehr gut sehen, nämlich daran, dass die Referenz auf den Vektor nicht konstant ist. Das weglassen von const soll immer implizieren, dass du das Objekt verändern möchtest und in dem Moment weißt du, dass das gefährlich ist, Elemente des Vektors ebenfalls als Referenz zu übergeben.

    Das ist doch reine Spekulation aufgrund von subjektiven Konventionen. Die Funktion könnte sehr wohl sicher implementiert sein. Vielleicht hat der Autor sich die Benutzung genau so gedacht, wie ich es geschrieben habe. Es gibt nur keine Möglichkeit das in C++ ohne einen Kommentar zu kommunizieren: Weder dem Compiler, noch dem Menschen. Beides geht in Rust in den meisten ähnlichen Fällen.



  • Jodocus schrieb:

    Doch, das kann man sehr gut sehen, nämlich daran, dass die Referenz auf den Vektor nicht konstant ist. Das weglassen von const soll immer implizieren, dass du das Objekt verändern möchtest und in dem Moment weißt du, dass das gefährlich ist, Elemente des Vektors ebenfalls als Referenz zu übergeben.

    Wie ist es mit folgendem Problem: Man hat eine GUI-Library, bei der jedes Objekt eine Liste mit Child-elemente sowie eine Event-listenern hat. Dann erstellt man einen Button, der beim Klicken ein Element loeschen soll (klassischer Close-Button von Fenstern). Der naive Ansatz ist, einen Eventlistener zu erstellen, der beim OnEvent den Loeschbefehl ausloest. Bei diesem Design kann es aber vorkommen, dass der EventListener seinen direkten Parent direkt oder indirekt loescht und diese aber noch in ihrer jeweiligen update-Methode stecken.
    In C++ ist das einfach hinschreibbar, denn beim Design der library denkt man, dass jedes Element einen owning-pointer auf die Childreferenzen hat und der Eventlistener einen non-owning auf das zu loeschende.
    In Rust protestiert der Compiler, sobald man das so aufgeschrieben hat und man muss sich schon einen anderen Designansatz ueberlegen.



  • @ TyRoXx:
    Ja, alles ziemlich an den Haaren herbeigezogen. Die Verwendung von back_inserter macht nur noch offensichtlicher, dass der Vektor verändert werden soll. Wie gesagt, keine Sau schreibt Code wie:

    vector<string> result; // hm, die API erwartet einen Result-vector, der dann befüllt wird
    result.push_back("Name"); // hm, pushen wir einfach mal aus Jucks den String da rein
    find_similar_names(result.back(), begin(result), end(result), back_inserter(result));
    

    Es gibt überhaupt keinen Grund, den String in den Vektor zu schreiben (außer man will einen flapsigen Versuch starten, ein Real-World-Problem in C++ zu demonstrieren).

    @ marthog: Irgendwie blöd, der Programmiersprache die Schuld am kaputten Klassendesign zu geben, oder?

    Sorry, ist mir alles zu blöd hier. Rust ist eine interessante Sprache, aber hier wird ja nur wieder mit religiösem Eifer versucht, Rust irgendwie zu rechtfertigen und als absoluten C++-Nachfolger zu propagieren, anstatt sich einfach mal mit den anderen Semantiken zu beschäftigen, ohne sofort wieder auf C++ rum zu bashen. Bin raus.



  • Jodocus schrieb:

    Es gibt überhaupt keinen Grund, den String in den Vektor zu schreiben (außer man will einen flapsigen Versuch starten, ein Real-World-Problem in C++ zu demonstrieren).

    Stimmt, ich hatte übersehen, dass es überhaupt keinen Grund gibt, Bugs bezüglich Speichersicherheit zu produzieren. Deswegen passt das ja auch nie.

    Es ist auch völlig unmöglich, dass der von mir gezeigte Code durch gut gemeinte, aber nicht zuende gedachte Refaktorierung entstanden ist.

    Jodocus schrieb:

    anstatt sich einfach mal mit den anderen Semantiken zu beschäftigen, ohne sofort wieder auf C++ rum zu bashen. Bin raus.

    Kann mir mal jemand eine Stelle in dem Thread zeigen, an der "auf C++ rumgebasht" wird?

    Was die "C++-Verteidiger" so von sich geben, liest sich ziemlich typisch. Da hätte man direkt aus einem C-vs-C++-Thread kopieren können. C++ durch Rust ersetzen, C durch C++, fertig ist der Real Programmer Talk. Alles, was neu ist, braucht kein Mensch. Das haben wir früher nicht gebraucht, das brauchen wir jetzt auch nicht. Aber da müsste man ja etwas Neues lernen. Wofür brauche ich dann noch meinen sechsten Sinn für undefiniertes Verhalten?

    Ich mache jedenfalls ständig in C++ Fehler, die mit Ownership zusammenhängen. Das wird auch nicht weniger mit der Zeit. Ich weiß nur inzwischen besser, wie man die Ursache findet. Am liebsten wäre es mir, wenn der Compiler mir dabei helfen würde. Schon sind wir bei Rust. Und ich bin nicht der einzige, der diesen Gedankengang nachvollziehen kann.


Anmelden zum Antworten