Swift for Linux kernel module?

Is Swift memory-safe as good as Rust-lang?
Can swift be used for writing linux kernel module? Can this kind of experiment happen in swift community?

https://www.phoronix.com/scan.php?page=news_item&px=Google-Wants-Rust-In-Kernel

Swift and Rust are both memory-safe, yes.

As for whether Swift could be used in a linux kernel module today, as it is? No. Even Rust will need some substantial changes as part of this effort. See this response from Linus:

Because kernel code is different from random user-space system tools.
Running out of memory simply MUST NOT cause an abort. It needs to
just result in an error return.

With the main point of Rust being safety, there is no way I will ever
accept "panic dynamically" (whether due to out-of-memory or due to
anything else - I also reacted to the "floating point use causes
dynamic panics") as a feature in the Rust model.

If you're looking for lower-level systems-y projects that could adopt Swift, personally I think WebKit is a more achievable goal. After all, memory safety issues in browser engines were what motivated Mozilla to create Rust, so you'd think WebKit would have similar motivation to move to a memory-safe language. Much of the development on WebKit is driven by Apple's engineers, so it seems natural that they would prefer Swift, and Apple probably have great resources internally to help their developers learn the language. Also, Swift has some experimental support for C++ interop, which would probably be really valuable for a project like WebKit that can't transition overnight.

Right now, probably the biggest challenge they'd face (and I'm not a WebKit developer, and haven't asked them, so this is just speculation) is cross-platform tooling. The WebKit repository does include an Xcode workspace, so at least some of the engineers working on it appear use Xcode (which has great Swift support), but it supports a wide variety of platforms and has many contributors who likely use other editing environments with less-excellent Swift support.

Just MHO: If WebKit did start using Swift for some critical components, that would be a real coming-of-age moment for the language and help iron out some of the larger issues that need to be resolved to use Swift in systems-level programming.

5 Likes

For that Swift would need to move forward in implementing the memory model proposed into the "ownership manifesto".

And I have a feeling that there's not enough energy to pursuit that goal or there's not enough time and priority to implement them.

But with those changes in place, Swift would also be able to compete in platforms only C, C++, Rust and Zig are suited to run. Swift have a problem that it needs to do this without taking its training wheels off, so for current users nothing changes and they can keep using the ARC based runtime management, but can go for those features once there's a need for that.

Those changes are necessary because there's a need to manually control memory lifetime and to be able to have a "freestanding" environment where you can run a program with no runtime at all so you can also write kernel code.

BTW: With direct access to C++ code and supporting changing the memory management to manual, Swift would become the most sane option for possible rewrite efforts, even more than Rust..

2 Likes

I love Swift but realistically any real time / systems mode for Swift would be substantially different to regular Swift that it might not be worth the context switch. Swift should instead focus on providing great interop to other system languages (in addition to c).

Zig looks like a great contender here. See example integration: https://twitter.com/DNAutics/status/1406825313280172038

1 Like

Might be offtopic, but if you are looking for a nice abstraction over C, I find Vala with the posix Profile an interesting option for embedded and kernel development.

Although there are parts of a kernel that have sensitive requirements like that, most of a kernel is really like any other large software project and needs common facilities for memory management, data structures, and object registration and discovery like Swift and its frameworks provide in userspace. It isn't out of the question to use a higher-level language for the majority of a kernel's code.

4 Likes

As Joe Groff said, a lot of kernel code may as well be written in a
high-level language like Swift.

Significant risks lurk in device drivers written in C. The behavior
of drivers for bus-mastering devices is more nuanced and brittle than
their sources typically acknowledge. High-level, low-cost abstractions
for peripherals, buses, interrupts, and shared memories could help
developers write reliable drivers for high-speed devices more quickly
and easily than they do now. I don't see strong reasons to suppose that
Swift cannot offer equally useful abstractions for device drivers as
Rust can.

Dave

Swift is much, much safer than C, but the memory safety here is far from being as good as rust.

  • in rust compiler prevents you from doing anything unsafe without using the unsafe keyword. In Swift, unsafe is just a convention, and compiler completely ignores it.
  • in rust the code is clearly divided into unsafe, and safe sections. In swift there's no clean delineation like that
  • some things in the standard library are unsafe, even though they don't use the word "unsafe", for example Unowned type
  • C and Objective-C functions are imported without any indication that using them is unsafe
  • you can have undefined behavior in pure swift, for example by using threads
  • there's no good way to know which operations are safe, and which are unsafe. In rust I can be fearless, and know that no matter what I do in safe rust it would be safe.
3 Likes

I'm not sure if it's technically, pendantically correct to say that you can create multiple threads using pure Swift; ultimately you'd be calling down to some C function to do it :stuck_out_tongue:

On your other points: I agree. I wish Swift was stricter about separating safe/unsafe code. For instance, I really, really dislike that String.withUTF8 { ... } gives you an unsafe buffer. It's just too easy, too convenient. If you're doing a lot of low-level text processing, it can be easy to forget that the UTF8 bytes exposed via that method do not bounds-check memory accesses, and bugs in your code could lead to memory safety violations.

1 Like

I don't think it needs to. Once the primitives are in place, the current mode can be only a "sugarized" version using sane defaults.

Apparently Swift is going for the use of keywords like 'strong', so if the compiler don't see that special keyword into a member variable is would just assume its the default current ref-counted approach.

One problem that appears is about the move semantics and how that might affect method declarations giving in the current ref-counted there's no need for passing ownership, while in the new 'strong' case you might pass ownership of the 'strong' reference or might not and i don't see a way of doing this without the developer annotating functions with this intent. So here is probably a contentious topic of how make this work without breaking current code.

Of course if this can be done in a automated way like if a 'strong' variable is declared within a function an later passed over to another function its clearly a move and if you reset a member variable with such a strong ref its also a move..

But i'm not a compiler guy, much less a Swift compiler guy, so i'm probably making a fool of myself by now, also i'm pretty sure a lot about of how this will work in practice is defined on that manifesto already with all the solutions to those issues.

But the point here really is about whether this all can be done without breaking current code and defaulting the Swift code out there to the sane defaults. If is doable, there's nothing to be afraid of giving the current Swift code would look the same and behave the same way

1 Like

I'm not sure if it's technically, pendantically correct to say that you can create multiple threads using pure Swift; ultimately you'd be calling down to some C function to do it

I think he is talking about concurrent access by multiple threads, which Rust catches on compiler time, while Swift would catch in runtime. But in this case Swift is preventing this from happening by convention and with the current concurrency model you will have a safe multi-threaded code.

So both are safe here, but in different ways.

But i disagree with both about the unsafe keyword as in practice is just a warning and don't actually prevent you from writing the said unsafe code in the first place. For instance what would wrapping the String.withUTF8 with a unsafe keyword do, if you still have to write the unsafe portion of the code anyway? It would basically have the same effect as a comment warning other coders about taking care when dealing with the inner buffer of the string.

In the case of the Rust compiler it makes sense because it signals that you don't want the compiler to track lifetimes and treat as a normal Rust code and the normal assumptions will be broken into those portions of code.

Now addressing some of the cukr comments:

Swift is much, much safer than C, but the memory safety here is far from being as good as rust.

I think this is highly speculative and to answer that question we would need to have clones of at least medium size projects for some scenarios and have them both in Swift and Rust to see which language would end with the safest programs.

While Rust can have a strong case against C and even C++ its not the same when comparing to Swift here, as in Rust the unsafe{} portions of code would be there anyway for the same cases that Swift would but just without using a keyword for that. And by the way, there are code in Rust that needs to use unsafe in places where it should'nt need giving its impossible to write "pure Rust" code to achieve the same things.. so how much of those cases will occur in real life programs, because Swift don't need to resort to unsafe code to write normal collections algorithms.

The real only case here is about the Rust compiler catching concurrent access at compile time, but in Swift this is now mitigated with the new concurrency framework. This is the same strategy pursuit by Go where people can create concurrent code without having to worry about this kind of thing.

So in the end, i'm not as sure as you that "Swift memory safety is far from being as good as Rust". I would said they are on-par.

The cool thing with Rust is that it achieves memory safety without resorting to automatic memory management. So there's not much of a point to compare Rust's safety when compared with languages like Swift, C#, Java or Go, except for the multi-thread scenario but that cannot be used as an argument at against Go and Swift now.

unsafe doesn't disable the borrow checker, it just lets you do some things that are otherwise forbidden. Unsafe lets you read or write through a pointer, access static variables, call unsafe functions, implement unsafe traits, and access the fields of a union. It still does the usual lifetime tracking.

2 Likes

It cant disable the borrow checker for things that are outside of the unsafe{} scope, and that's what you want anyway, but it wont track things inside the unsafe block, otherwise you would not be allowed to write unsafe code in the unsafe block.
Things (declared) inside the unsafe block cannot be tracked in the same way otherwise the code would not compile when you mess with C buffers for instance for which the lifetime and other compiler goodies cannot not controlled by the Rust compiler in the same way.

Yes, I meant concurrent access by multiple threads. Today swift doesn't catch that at runtime (unless you're running with the thread sanitizer) Even with the new concurrency model, swift will be thread-unsafe by default, with the fix for that not coming for at least a year from now because of source stability.

If you assume that you have to write unsafe code, then of course the conclusion is that you have to write unsafe code.

For me the "safe language" is not about what happens if you want to write unsafe code. It's about what happens if you want to write safe code. For me safe language should make sure that if you want to write safe code, it will be safe.

Let's check what happens if you don't need to write the unsafe portion of the code.
First I write some code that does what I want

func printBytes(_ str: inout String) {
    str.withUTF8 { bytes in
        for byte in bytes {
            print(byte)
        }
    }
}

In today swift I would be done. I used unsafe constructs, but I had no idea about that. There are no comments warning me, because I had no idea that I should be wary.

In hypothetical swift that requires the use of unsafe keyword, the compiler would yell at me to add that. It would force me to realize that what I just wrote is accidentally unsafe. It would make me look for another, safer way to do what I wanted to do. I would rewrite the function to look closer to this:

func printBytes(_ str: inout String) {
    for byte in Data(str.utf8) {
        print(byte)
    }
}
2 Likes

I would really like to see Swift moving towards this. It would make it so much easier to check your assumptions and to audit your code for unsafety.

Could unsafe be an effect like throws and async? It seems like it would slot into the existing model for effects pretty well:

  • If you call an unsafe function from a safe function you get an error saying "unsafe function cannot be called from a non-unsafe context".
  • To call an unsafe function from a safe context you would need to fence it with unsafe { }, similar to Task { } and Task.detached { }.
  • We could even have reunsafe, similar to rethrows, which would make higher-order functions unsafe when passed an unsafe function.
2 Likes

A few observations of some things people might want in a low-level context such as a kernel, browser engine, virtual machine, or bare-metal system:

2 Likes

Swift is much, much safer than C, but it has its own advantages and new security features.

I should have been more careful with my wording, as with "catch on runtime" i was implying that the program would just break or segfault when the concurrent access (without a proper synchronization guard) happen..

I agree with you that the compiler can be more smart and catch "rookie mistakes" with at least a warning helping to show how the api is being misused.

For instance in the example given:
withUTF8 {} or withCString{}

For me is a helper to interface with other systems, like passing for a C API or when you need to send it over network, or when you need any sort of copy and composition with other things. If you have to use it for other tasks like your example, or you are missing some api somewhere or the String type is in debt and there's a need for an upgrade.

But apart from that, it has "unsafe" written in its forefront, but it does the right thing: the buffer is scoped based with a limited lifetime inside the scope. For me this is one of the reasons to use unsafe{}, its also about scoping.

In the case of Rust you can wrap a bunch of unsafe compositions in a single scope, which in turn make sense to turn this into a keyword, but in the withCString{} case forcing it to be also wrapped with unsafe{} would cascade into some sort of visual pollution in a bigger codebase, giving you have to wrap a well known unsafe scope with another unsafe scope. Its not much and it even looks cool in a sample but it will get tired pretty soon as you write more and more code.

But i think its not a bad idea to have more annotations and warnings on unsafe methods. For instance the withUnsafeBytes of Data could have a "@unsafe" on top of it. This would help the compiler to make the proper checks inside that scope and maybe to warn the user about something.

With that in place maybe it would make sense to have an unsafe{} guard where you can write a bunch of scopes with the @unsafe keyword on them, the bigger scope (the unsafe{}) than would be used to control the lifetime of every buffer inside of it at once, instead of cascading scopes "{{{ }}}" you could access the buffers with a global lifetime defined to all of them at once.

I agree that it can be improved, but i think that if there's a less intrusive way of doing it, that should be pursuit instead of just copying something without the same benefits you see in the source being copied from as withUnsafeBytes{} or whatever are already scoped and with a proper control of lifetime not letting the buffer to leak, where holders could not possibly know whether the buffer they are holding is still there, specially now with a concurrent paradigm where things can be on TLS.

1 Like
Terms of Service

Privacy Policy

Cookie Policy