Announcement

Collapse
No announcement yet.

Rust For Linux Kernel v9 Patches Trim Things Down Greatly For Easier Upstreaming

Collapse
X
 
  • Filter
  • Time
  • Show
Clear All
new posts

  • ultimA
    replied
    Originally posted by NobodyXu View Post
    Code:
    #include <utility>
    
    template <class T, class ...Args>
    auto initialize(Args &&...args) -> T {
    T{std::forward(args)...}
    }
    People often write generic code that requires initialization of an object and having overloaded meaning when writing "T{...}" really isn't helping.
    If the type "T" has an initializer list constructor, things get much more complicated and cause confusion.

    So people just keep using the old "T(...)" instead.
    Now you are getting into a different territory. First, the kind of code you showcase would be a library function even in the kernel and would be replacing macros today, except being typesafe.

    Second, you really think people won't write hard-to-read code in Rust too? The code you show as an example is not something people would normally write even in C++.

    I also strongly disagree that it would be better to use template magic in C++ to emulate traits in Rust. I have shown in my posts above that you can perfectly and efficiently emulate traits in C++ with a simple, easy-to-implement and easy-to-read syntax (using inheritance and compiler optimizations). Why the hell would anyone want to use templates for this? This seems very contrived only to try to prove C++ must be harder to understand than Rust even in cases where it really isn't.
    Last edited by ultimA; 08 August 2022, 05:27 AM.

    Leave a comment:


  • NobodyXu
    replied
    Originally posted by ultimA View Post
    Just because a project implements dynamic dispatching (and in C of all cases) doesn't make it OOP-style.
    Read this please:

    Object-oriented design patterns in the kernel, part 1 - https://lwn.net/Articles/444910/


    Originally posted by ultimA View Post
    Compilers are actually known to optimize away even vtables completely if they can prove there are no current and future users for it. In a kernel application and after devirtualization this probably means compiling with LTO, which has only been possible since relatively recently with the Linux kernel, but nevertheless possible. We can also reasonably speculate that it would have gotten implemented much earlier if there had been a language binding in the kernel greatly benefiting from it.
    Not if the object is reference counted or is put into some kinds of container, which is extremely hard for compiler to remove the vtable pointer.
    And the Linux kernel supports kernel module and anything used in the API must be stable and the compiler cannot just remove a field from it.

    Thus, in practice, this devirtualization might not work that well.
    When you opt-in to use abstract class or virtual method in C++, you must consider its cost and cannot expect it to be zero-cost without some analysis on its specific use cases.

    Leave a comment:


  • ultimA
    replied
    Originally posted by NobodyXu View Post

    I highly doubted this is a problem since the Linux kernel itself uses OOP programming style.

    Needs a filesystem? Define a list of functions and put them in a vtable for it and file descriptor. You can also skip some functions and let the linux kernel provides its default implementation for you.

    Want a module? Define a list of functions and put them in a vtable.
    Just because a project implements dynamic dispatching (and in C of all cases) doesn't make it OOP-style.

    Originally posted by NobodyXu View Post
    It is not zero-cost in C++: The object you created would have a 8-byte vtable pointer regardless of whether you use it or not.
    Compilers are actually known to optimize away even vtables completely if they can prove there are no current and future users for it. In a kernel application and after devirtualization this probably means compiling with LTO, which has only been possible since relatively recently with the Linux kernel, but nevertheless possible. We can also reasonably speculate that it would have gotten implemented much earlier if there had been a language binding in the kernel greatly benefiting from it.
    Last edited by ultimA; 08 August 2022, 04:39 AM.

    Leave a comment:


  • NobodyXu
    replied
    Originally posted by Sergey Podobry View Post
    And you think Rust magically implements dynamic dispatch without adding extra data? You know you don't have to write kernel code with abstract factories and virtual inheritance everywhere. Don't know why but when people argue about C++ they think only about this kind of code.
    You misread my reply.

    ultimA, who I was replying to, wrote about a case where no dynamic dispatch but still used abstract class in C++ to emulate trait in Rust.
    That is the point I wanted to counter, because that is not zero cost in C++.

    A better solution would be of course, to use CRTP or concept to emulate trait in Rust, though the former is very hacky and the later needs people to either use boost, which uses a lot of hacks, or upgrade to C++20.

    Originally posted by Sergey Podobry View Post
    It's not a mess, it's versatility. And a lot of that comes from C. You can write in C:
    Code:
    my_struct a;
    my_struct a = b;
    my_struct a = { 0 };
    my_struct a = { .data = 0 };
    my_struct a = { .data = 0, .val = 2 };
    my_struct a(b);
    .
    Let's think about this piece of code:

    Code:
    std::vector<int> v = std::vector{10};
    This person who wrote this code wants to have 10 int initialized to 0, but instead they have a vec which has only one element, 10.

    You might say that this person just needs to take more time to read the cpp language reference https://en.cppreference.com/w/cpp, but then what about this:

    Code:
    #include <utility>
    
    template <class T, class ...Args>
    auto initialize(Args &&...args) -> T {
        T{std::forward(args)...}
    }
    People often write generic code that requires initialization of an object and having overloaded meaning when writing "T{...}" really isn't helping.
    If the type "T" has an initializer list constructor, things get much more complicated and cause confusion.

    So people just keep using the old "T(...)" instead.

    Leave a comment:


  • Sergey Podobry
    replied
    Originally posted by NobodyXu View Post
    It is not zero-cost in C++: The object you created would have a 8-byte vtable pointer regardless of whether you use it or not.
    And you think Rust magically implements dynamic dispatch without adding extra data? You know you don't have to write kernel code with abstract factories and virtual inheritance everywhere. Don't know why but when people argue about C++ they think only about this kind of code.

    Originally posted by NobodyXu View Post
    IMHO the real reason for rejecting C++ is because it is a mess.

    Its language design is inconsistent and overly complex for no benefit, e.g. you have at least 3 ways to initialize an object and two of them have the same syntax (list initialization and initializer list initialization).

    And initializer list initialisation also messes with default initialization using "auto a = Class{};".
    It's not a mess, it's versatility. And a lot of that comes from C. You can write in C:
    Code:
    my_struct a; 
    my_struct a = b;
    my_struct a = { 0 };
    my_struct a = { .data = 0 };
    my_struct a = { .data = 0, .val = 2 };
    my_struct a(b);
    .

    Leave a comment:


  • NobodyXu
    replied
    Originally posted by ultimA View Post

    First reason is, the difference is an implementation detail, but Linus's objection was to the programming style, and that is still very much OOP even in Rust (as far as the proposed kernel framework is concerned). You still have an inheritance hierarchy where types serve as organizational units for methods and/or data - one of the major if not the defining characteristics of OOP.
    I highly doubted this is a problem since the Linux kernel itself uses OOP programming style.

    Needs a filesystem? Define a list of functions and put them in a vtable for it and file descriptor. You can also skip some functions and let the linux kernel provides its default implementation for you.

    Want a module? Define a list of functions and put them in a vtable.

    Originally posted by ultimA View Post
    Second reason is, you can actually get the exact same kind of inheritance in C++. Use an abstract base class for your interface type, give the derived class the "final" keyword, and at call site use the known derived type instead of the base's type. This will give you the functionality of an interface without any virtual calls thanks to the devirtualization done in the compiler. In other words, you could do the same in C++ as in Rust with no difference in either function or efficiency for a kernel framework.
    It is not zero-cost in C++: The object you created would have a 8-byte vtable pointer regardless of whether you use it or not.

    IMHO the real reason for rejecting C++ is because it is a mess.

    Its language design is inconsistent and overly complex for no benefit, e.g. you have at least 3 ways to initialize an object and two of them have the same syntax (list initialization and initializer list initialization).

    And initializer list initialisation also messes with default initialization using "auto a = Class{};".

    Leave a comment:


  • ultimA
    replied
    Originally posted by ssokolow View Post

    Bear in mind that there are two different types of inheritance.

    C++ was built around the 80s/90s obsession with implementation inheritance (i.e. subclassing).

    Rust is about as aligned to that as C is. Rust supports interface inheritance. (i.e. You can declare that, to implement trait/interface A, you must also implement traits B and C, and then anything that specifies trait A in its interface constraints can also use methods defined by traits B and C. That's it.)
    True, but totally irrelevant for two reasons.

    First reason is, the difference is an implementation detail, but Linus's objection was to the programming style, and that is still very much OOP even in Rust (as far as the proposed kernel framework is concerned). You still have an inheritance hierarchy where types serve as organizational units for methods and/or data - one of the major if not the defining characteristics of OOP.

    Second reason is, you can actually get the exact same kind of inheritance in C++. Use an abstract base class for your interface type, give the derived class the "final" keyword, and at call site use the known derived type instead of the base's type. This will give you the functionality of an interface without any virtual calls thanks to the devirtualization done in the compiler. In other words, you could do the same in C++ as in Rust with no difference in either function or efficiency for a kernel framework.
    Last edited by ultimA; 07 August 2022, 09:19 PM.

    Leave a comment:


  • ssokolow
    replied
    Originally posted by ultimA View Post
    Rust in comparison provides certain safety guarantees even if you implement something in non-OOP-style. In practice though the proposed Rust modules for Linux rely heavily on objects and inheritance, It is possible Linus' opinion of OOP has changed over the years, but if I'd have to guess he's just answering to corporate interests by allowing Rust in the kernel.
    Bear in mind that there are two different types of inheritance.

    C++ was built around the 80s/90s obsession with implementation inheritance (i.e. subclassing).

    Rust is about as aligned to that as C is. Rust supports interface inheritance. (i.e. You can declare that, to implement trait/interface A, you must also implement traits B and C, and then anything that specifies trait A in its interface constraints can also use methods defined by traits B and C. That's it.)

    Leave a comment:


  • ultimA
    replied
    Yes, Linus rejected C++ hardly back then. It seems though most people only know this through hearsay and hardly anybody knows/remembers the actual mail he wrote about the reasons. Due to this, an important detail seems to have gotten lost over the time: He rejected C++ exclusively or almost exclusively due to the object-oriented programming style. Strip OOP from C++ (meaning for example not being able to uses classes and RAII) and you're left without any meaningful improvement over plain C.

    Rust in comparison provides certain safety guarantees even if you implement something in non-OOP-style. In practice though the proposed Rust modules for Linux rely heavily on objects and inheritance, It is possible Linus' opinion of OOP has changed over the years, but if I'd have to guess he's just answering to corporate interests by allowing Rust in the kernel.
    Last edited by ultimA; 07 August 2022, 05:14 PM.

    Leave a comment:


  • Sergey Podobry
    replied
    Originally posted by piotrj3 View Post
    This is far worse then just "plugging" kernel allocator into STD (what theoretically can be done). It also implies that if anything under the hood of STD (or any feature) can't use exceptions (oh they do use exceptions) among few other things. For example MSVC for windows has flags to force disable exceptions and RTII. Practically means you simply can't use std, stl, raii, exceptions among many other things. And at this point C++ dumps down a lot to almost the level of C.
    That's incorrect. For the kernel you just need to disable exceptions. Yes, some standard STL stuff won't work. But not the whole STL. So you end up with a stripped down version of STL and kernel specific containers that you use instead of std::vector or std::map. RAII, lambda, constexpr, template, overload, namespace, class, new/delete work fine and this is much more than C can do.

    It's not a theoretical hypothesis. I'm basing on my real practice for the Windows kernel drivers.

    Leave a comment:

Working...
X