rdevd a devd re-implemented on Rust

Ok, this "silver bullet" argument nobody brought up is a "dangerous idea", but ...
It's always implied around arguments like "hey, we've had a history of security issues, we have to move to a different language to fix this".

... this is not? Seriously? :-/
What ... this list of rules to follow? No, it's the exact opposite regarding the mindset needed. It's "I have to invest thought into the design of my program to make it secure" instead of some "the language will safeguard anyways"...

Reality just doesn't work that way. Take FreeBSD src for example. As a reviewer I'm often confronted with overly long functions, switches, goto sections and monster loops where it's difficult to check all possible exit paths. And it's not that this code was written by amateurs without any sense of design and structure.
Do you have an example (link to phabricator)? And then, why don't you request reducing the complexity there? Just picking one example from that list, factoring out the body of a "monster loop" into a few desciptively named (static) functions doesn't cost you anything regarding performance with a sane compiler.

C lacks some essential features to support safe programming (e.g. RAII),
That's simply not true. And on a side note, I think RAII (which is only essential for "safety" with exceptions) is a particularly bad idea, it enforces creating (technically motivated!) classes for each and every technical resource.

and the economics of C programming incentivize some bad programming constructs. It's not a shame to admit that, regardless of what one thinks about Rust - C was designed in a different time.
And that's a way to describe "lazy" programming... 🤷

BTW, there's no significant difference in runtime performance between C, C++ or Rust. Once compiled, relative performance stays within one digit percentage, with no language a clear winner for all sorts of tasks. Libraries are a different topic though.
Can't say much about that, other posts in this thread suggest the opposite ... I didn't do any experiments and analysis myself.
 
...giving compilers great freedoms with automatic optimizations. Still, C can't support what you outlined here.
As I was saying, NO language lets me express the constraints I want placed on the data and execution, leaving the compiler complete freedom to rejigger the entire application. All these new languages just "nibble at the edges", leaving the effort (and confidence in it) largely unchanged (in my 50 year career).

I.e., perhaps some REAL thought needs to go into the design of languages instead of just playing with features?
 
Take FreeBSD src for example. As a reviewer I'm often confronted with overly long functions, switches, goto sections and monster loops where it's difficult to check all possible exit paths. And it's not that this code was written by amateurs without any sense of design and structure.
That's a silly example. Should we appoint a "code czar" empowered to reject any submissions that don't meet some arbitrary standards (that we will likely spend YEARS trying to come to consensus agreement)? How many "volunteers" will throw up their hands and give their time, elsewhere?

I'm a stickler for code correctness (in my products -- because they actually do things (like activate mechanisms). I grumble when I encounter code that obviously was poorly tested in released products (everyone has a regression test suite at hand, right??). When I talk to the developer(s), they invariably reply, "My boss doesn't give me the time to adequately test things." Yet, when the same developer is giving his time to a FOSS project -- no boss hanging over his shoulder dictating how he uses his time -- he STILL doesn't adequately test his code. Because he thinks someone else will find the problems.

[This is particularly true of FOSS: "all those EYES looking at the code will undoubtedly find any latent bugs..."]

[[Gee, have you noticed that if you EDIT a post, here, you can no longer use the "Quote" feature? How long has that behavior been part of the forum's design?]]

I build all ports from source. Why? Because I expect the folks who volunteer to build them aren't as pedantic about ensuring the build "happens" correctly.

I distinctly recall installing a prebuilt gnuplot, ages ago. As it comes with a comprehensive test suit, running that was obviously worth the few minutes of my time that it took. All I have to do is look at graphs and verify they "look correct"...

"Gee, THAT's not correct!" Obviously, the porter either didn't run the test suite OR wasn't aware of the actual shapes each graph should take. I.e., strictly speaking, the porter wasn't qualified to do the port.

And, using FBSD as an example burdens your analysis with a preexisting code base that is likely too large to refactor, completely, at this level of complexity. So, you end up putting lipstick on a pig because no ONE (nor GROUP) wants to undertake a complete rewrite.
 
Sure, you have to compare arrays with arrays in each language.
Why? If one language only has one way to access elements of an array, why burden the other with the use of that same mechanism?

If I know the compiler is going to layout a char array in successive memory locations and I want to clear all of those locations, why should I be forced to address each one individually? Why can't I cast contiguous groups of them to a larger sized data type and clear *those*, instead? Isn't the end result the same?

By that logic, if a language supports something that another doesn't, then it is the defacto "winner" of such a comparison? Even if the other language has a mechanism that can be invoked to meet the same goals?

What's the C construct for car? cdr? cons? (no, you can't invoke a function that does those things; the language has to directly support it, right?)
 
It's always implied around arguments like "hey, we've had a history of security issues, we have to move to a different language to fix this".

That's your reading. There are statistics that attribute CVEs and critical failures to different causes, and memory handling errors consistently rank among the top issues there. Nothing wrong with that argument.

What ... this list of rules to follow? No, it's the exact opposite regarding the mindset needed. It's "I have to invest thought into the design of my program to make it secure" instead of some "the language will safeguard anyways"...

This list of rules is common sense and almost everybody programming C tries to adhere. It just doesn't work well enough in practice, for any middle to large sized C project. The statistics are clear about that. If you call it "not that hard" to prevent security problems by just following your rules that's fooling yourself (and others).
And I understand what you mean by this "the language will safeguard anyways" mindset, but there's no evidence for that. Ultimately memory safety in Rust works by the compiler barking at you until you get the design right.

Do you have an example (link to phabricator)? And then, why don't you request reducing the complexity there? Just picking one example from that list, factoring out the body of a "monster loop" into a few desciptively named (static) functions doesn't cost you anything regarding performance with a sane compiler.

Uhm, that doesn't help with convoluted exit paths? Also it sounds nice in theory, until you have to transfer most of the local state into the static function in a long list of arguments, with some variables being mutable. Then you end up with scattered code and even less readability, and maintenance costs because it's easy to get the argument order wrong.
From the top of my mind, have a look at dsp.c, uaudio.c or sndstat.c for some examples.

That's simply not true. And on a side note, I think RAII (which is only essential for "safety" with exceptions) is a particularly bad idea, it enforces creating (technically motivated!) classes for each and every technical resource.

Thing is, if there was an option in C to make the compiler clean up on exit of a code block, everybody would use that in a heartbeat. Simplified code for memory allocation, mutex locking, open files and other types of resource handling errors, the potential benefits are huge. Nobody cares about technically motivated classes if it substantially cleans up the code and has no runtime costs.

And that's a way to describe "lazy" programming... 🤷

Yes, everybody is lazy sometimes, a compiler is not. That's the point.

Can't say much about that, other posts in this thread suggest the opposite ... I didn't do any experiments and analysis myself.

Don't know what the other posts are about, there's no context and they don't make sense to me. Every evaluation out there on the net will tell you that performance is comparable, except for some edge cases and specially crafted optimizations.
 
Why? If one language only has one way to access elements of an array, why burden the other with the use of that same mechanism?

Don't get what you're getting at - cracauer@ compared arrays in C to collection classes in other languages, why not compare it to arrays in said language?
 
Uhm, that doesn't help with convoluted exit paths? Also it sounds nice in theory, until you have to transfer most of the local state into the static function in a long list of arguments, with some variables being mutable. Then you end up with scattered code and even less readability, and maintenance costs because it's easy to get the argument order wrong.
From the top of my mind, have a look at dsp.c, uaudio.c or sndstat.c for some examples.
Well, first I said I'm just picking one example (which was the "monster loops"). No matter what, I'm absolutely convinced that every algorithm can be expressed in a readable way if you're willing to invest the work to do so. And yes, also with the language elements offered by C. I'll certainly have a look at the code files you mentioned. I know in practice, lots of code looks horrible (when porting software for FreeBSD, I had to look at enough horribly crappy stuff trying to understand WTF it does), but it's not like you couldn't do better ... 🤷

Thing is, if there was an option in C to make the compiler clean up on exit of a code block, everybody would use that in a heartbeat. Simplified code for memory allocation, mutex locking, open files and other types of resource handling errors, the potential benefits are huge. Nobody cares about technically motivated classes if it substantially cleans up the code and has no runtime costs.
Nothing against the idea to build something into the language that helps with proper cleanup. I mean, there's the "goto done" idiom to "solve" this, and used correctly, it results in well-readable code, but indeed it's not explicit, so no safeguards against misuse either. Something like having to mark references to object which need cleanup, having an explicit cleanup block (thinking of e.g. finally in some languages) and having the compiler check for omissions would certainly help a lot. Just saying that's a lot different from the RAII paradigm in C++ ...

Yes, everybody is lazy sometimes, a compiler is not. That's the point.
If you also look at my previous post, you'll see I don't doubt that. What I doubt is how the benefits, costs and risks are assessed by proponents of introducing Rust into the base system right now. E.g. I think the risk of potential future changes to the language breaking and obsoleting code written today is too high without having some independent standard for the language specification like it's the case for C and C++.
 
Don't get what you're getting at - cracauer@ compared arrays in C to collection classes in other languages, why not compare it to arrays in said language?
An array, class, etc. is just a concept -- that is reify by a particular compiler for a particular language in a particular way. You are free to implement arrays, classes, etc. anyway you choose. A language (or compiler for that language) makes one such choice.

There is nothing that says an array has to be a contiguous block of memory -- despite our THINKING of it as a contiguous collection of similar typed objects. If your language only allows accessing an array by index, then you could scatter the elements throughout memory with a table of pointers referencing their individual elements. The array index would act as an index into this table and the pointer therein would be dereferenced to access the element.

Or, a linked list scattered through memory that can periodically be compacted to reclaim gaps between elements.

Clearly such an implementation would suffer (performance wise) relative to one where elements are contiguous AND accessible by manipulating a pointer.

But, the impact of such a performance hit might not be significant. E.g., for small RTOS's, I typically store each tasks state on its own (private) stack -- instead of gathering all of this information into one place "in the kernel". Linking these stack frames together lets me find any task and its stored state. Because I don't tend to walk down these lists at the application layer, chasing a pointer per task switch is inconsequential vs. accessing an array of structs.
 
What I was saying is that most languages don't have arrays with the speed properties of C arrays.

Well in our context, C++ has them, and in Rust you have to use unsafe in the very few cases where you don't need a boundary check and it really has a performance impact. That is, after the compiler and the CPU tried their best to eliminate the performance impact. Rust has other edge cases where it is faster than canonical C.

Well, first I said I'm just picking one example (which was the "monster loops"). No matter what, I'm absolutely convinced that every algorithm can be expressed in a readable way if you're willing to invest the work to do so. And yes, also with the language elements offered by C.

Agree with that for problems which are usually solved by algorithms. But not in case of managing large, interdependent state mixed with async execution, callbacks and error handling. Most C based GUI frameworks failed hard in the 90s because of that. The few that remain emulate some sort of OO, and are still less capable.

I'll certainly have a look at the code files you mentioned. I know in practice, lots of code looks horrible (when porting software for FreeBSD, I had to look at enough horribly crappy stuff trying to understand WTF it does), but it's not like you couldn't do better ... 🤷

Definitely with you there, Amen!

An array, class, etc. is just a concept -- that is reify by a particular compiler for a particular language in a particular way. You are free to implement arrays, classes, etc. anyway you choose. A language (or compiler for that language) makes one such choice.

That's a bit philosophical, no? If you mention "array" to a C, C++, or Rust programmer, and to their respective language designers / compiler developers, they all understand the same thing with the same runtime properties. OTOH, containers like "list" tend to be implemented in different ways.
 
Well in our context, C++ has them, and in Rust you have to use unsafe in the very few cases where you don't need a boundary check and it really has a performance impact. That is, after the compiler and the CPU tried their best to eliminate the performance impact. Rust has other edge cases where it is faster than canonical C.

Does Rust also allow creation of such an array without element initialization?

It's not just the lack of bounds checking that makes C arrays fast.
 
Does Rust also allow creation of such an array without element initialization?

It's not just the lack of bounds checking that makes C arrays fast.

It's possible, but requires unsafe too. I would expect them to streamline this if it really is a common problem. Any particular use case you have in mind?
 
It's possible, but requires unsafe too. I would expect them to streamline this if it really is a common problem. Any particular use case you have in mind?

Yes, I often allocate an array bigger than what I need, use a few elements and then let go of it.

Having to initialize not only the elements that I actually use but also the elements that stay unused to the end can significantly impact performance.

Initialization of arrays, if you do want it, is also cheaper if you want the same thing in each cell (such as zero), because you can use one bzero() call on the whole thing. Whereas most other languages "properly" initialize by doing each array element individually.

Also, if the array is global it will be initialized to zero for free.
 
Agree with that for problems which are usually solved by algorithms. But not in case of managing large, interdependent state mixed with async execution, callbacks and error handling. Most C based GUI frameworks failed hard in the 90s because of that. The few that remain emulate some sort of OO, and are still less capable.
That's kind of a funny coincidence because my most recent coding project is an X11 application without toolkit and as a consequence, it was my first time bothering to implement polymorphism in C. I mean, when thinking about how to abstract your typical GUI, the problem more or less shouts "OO model" at you. 🙈 (and btw, before anyone accuses me of refusing to use the best tool for the job, there's a previous implementation of the same tool in C++ using Qt, and I ran into a dead end with that trying to solve some performance issue...)

In fact, almost all of my software written in C uses at least some basic OOP, and that's actually also the case for lots of software written by others. I wouldn't call it "emulating" though, after all, OOP is a concept and whether it's (partially) implemented in the language you use or you do it all yourself is an implementation detail in the end. You could even argue C implements parts of OOP concepts, I mean you do have "objects" (this term is used a lot in the standard), they can contain structured data of complex types (structs) and you have typed pointers as references to them ... 😁

Finally getting to my point (or question) though: I don't get how this is related to the "problem description" you gave? I don't see why I shouldn't be able doing all this while breaking down my code into understandable functional units interacting with each other?
 
That's a bit philosophical, no? If you mention "array" to a C, C++, or Rust programmer, and to their respective language designers / compiler developers, they all understand the same thing with the same runtime properties. OTOH, containers like "list" tend to be implemented in different ways.
Nothing in the machine is "real". It is all a set of abstractions with which we associate meaning. The machine, itself, may also be an abstraction -- running inside some other machine.

How does an associative array differ, semantically, from a "classic" array? Are you sure you know how they are stored and implemeted at runtime? <grin>
 
Yes, I often allocate an array bigger than what I need, use a few elements and then let go of it.

Having to initialize not only the elements that I actually use but also the elements that stay unused to the end can significantly impact performance.

Never occurred to me, except for small local strings and inside containers (e.g. vector). If it really does impact performance you should probably pool the memory and avoid the costly allocation anyway?
 
That's kind of a funny coincidence because my most recent coding project is an X11 application without toolkit and as a consequence, it was my first time bothering to implement polymorphism in C. I mean, when thinking about how to abstract your typical GUI, the problem more or less shouts "OO model" at you. 🙈 (and btw, before anyone accuses me of refusing to use the best tool for the job, there's a previous implementation of the same tool in C++ using Qt, and I ran into a dead end with that trying to solve some performance issue...)

Interesting - care to explain which part was the performance bottleneck? The usual suspects like graphics are well optimized in Qt and things like IO can be substituted easily.

In fact, almost all of my software written in C uses at least some basic OOP, and that's actually also the case for lots of software written by others. I wouldn't call it "emulating" though, after all, OOP is a concept and whether it's (partially) implemented in the language you use or you do it all yourself is an implementation detail in the end. You could even argue C implements parts of OOP concepts, I mean you do have "objects" (this term is used a lot in the standard), they can contain structured data of complex types (structs) and you have typed pointers as references to them ... 😁

I'm sure it's a nice exercise, but anything beyond simple "objects" needs a lot of boiler plate code to implement inheritance, vtables, type inference, etc. Not only in the implementation, but also when using it somewhere else.

Finally getting to my point (or question) though: I don't get how this is related to the "problem description" you gave?

The problem is managing scattered but interdependent state, with async calls from different ends (API, syscalls, interrupts), possibly multithreaded. You get that both in GUI frameworks and in OS kernels. The length of "Monster loops" is not a problem per se. But when locking and resource allocation are combined with C style error handling, they tend to have convoluted exit conditions, and / or exit paths when using goto.

I don't see why I shouldn't be able doing all this while breaking down my code into understandable functional units interacting with each other?

Good question, I'm more familiar with the unpleasant outcomes than the process that creates it. Still thinking about it, but my theory is that it's caused by the limited composability in C. You can basically delegate code execution and data manipulation to a function you call, that's it. What you cannot delegate is things like local control flow, type inference or resource and mutex management. At best you can put some of that into macros, but macros don't compose well either.

Since you can neither delegate this work to the compiler nor to function calls, it means you have to write more boilerplate code. While often just a bit verbose, it adds up and becomes problematic in cases described above.
 
Nothing in the machine is "real". It is all a set of abstractions with which we associate meaning. The machine, itself, may also be an abstraction -- running inside some other machine.

How does an associative array differ, semantically, from a "classic" array? Are you sure you know how they are stored and implemeted at runtime? <grin>

Yes. I'm an engineer, not a philosopher ;)
 
Never occurred to me, except for small local strings and inside containers (e.g. vector). If it really does impact performance you should probably pool the memory and avoid the costly allocation anyway?

I lived of preallocated and reused arrays extensively in a previous job. That was in Common Lisp and avoided garbage collection.

It works kinda well in query-oriented systems. You use preallocated space for the duration of one query and then reset it between queries. Blazingly fast. No stirring the heap at all.

But I found that it quickly goes out of hands if you want to (or have to) do any kind of "giving back" of future-unused storage in there during the query. For some data structures that was required so that storage requirements wouldn't get too large. Once there you quickly implement a malloc/free system - which has been done better before.

I find that using the given memory allocation scheme (garbage collector or malloc/free) is more comfortable. You just need to do it well and not work against it.

Among other reasons there is tools. Tools for debugging GC or malloc/free are common. There are no such tools for your preallocated arrays.
 
I am a developer of freebsd-kpi-rs and freebsd-kmi-rs (R4FBSDk - Rust for FreeBSD kernel). Recently I have released the devd(8) re-implemented in Rust (ported from closed source code which I developed for the client) and I was planning to submit port for review. But, for reasons beyond my control, I decided to terminate both projects and focus on the new projects i.e RISC-V/ARM bootloader (Rust) and microkernel (Rust) with userland process schedulers, for the embedded devices i.e routers, SCADA etc. Both are focused on European market only, so in US you won't hear about it a lot.
I am not going to maintain the code anymore, so if you really need it, you can download it from the f[olo]wing repos (access is not available from some countries).
1) rdevd a devd device state change daemon which is 100% compatiable with original devd with some exceptions, a project page: project page
2) R4FBSDk prject page and FreeBSD Kernel Programming Interface and Kernel Module Interface
KPI is a FFI to kernel
KMI is kernel module standart library which used KPI and turns it into Rust friendly safe code. i.e like libc crate and nix crate.
+ cargo based build kernel module template a template of the kernel module without FreeBSD build system (best option)
+ makefile based kernel module template atemplate of the kernel module based on makefile which builds the module as a library and attaches it to C file (bad approach)
What you did was great. It's good that you still have it available. Though, since it was closed source, they would have to give permission for it to be available as open source or give permission for it to be used, unless it is under your ownership itself.
The reasons for termination are:
You don't have to give us many of your reasons. They're your reasons, and reasons that aren't due to software and the software ecosystem are better kept to yourself, as you don't owe an explanation to us for those.
Also, threre are other reasons which are not really important for me, but also significant. One reason is some members FreeBSD community so arrogant that they believe that they are masters of memory management and they belittle my work by calling it "Rust sh@t". I, personally, don't care because I also doing this way quite often (even calling Rust this way). I am not big fan of Rust, but it is way better than C and C++ for sure. Provide better alternative and I will be happy to use it. And, I don't want to start a holywar, but it's not like that. In a large project it's very difficult to track and remember where the memory was allocated and how it was allocated. And I noticed that I am spending less time debugging the problems related to memory access violations and use after free problems or invalid pointers or out of bounds reading or even integer overflow.
And finally, I don't see any advantages of FreeBSD over GNU/Linux any more. Only two advantages still actual: there is no Linus Torvalds and FreeBSD is still optimal as it can.
That's a good example for the benefits of the use of a modern language like Rust. IMO, there should be a separation of C family code and Rust code within FreeBSD. It would need a separate project that uses Rust, where exclusively C and C++ code is in the FreeBSD project, and additional Rust code is its own complementary project. There's a divide of those who think that C is the best, where C++ can make up for additional needs, and those who believe a modern language needs to be. Rust is one such language, and it is a pioneer. Zig would be the closet which is in the C language family, while it can compile C, but the needed language may not exist yet. Zig may be the precursor to that future language, while there's a lot that can be learned from Rust for it.

I wonder if you've heard of Redox, and a few other Rust based smaller operating systems. Redox lacks networking for use of the Internet, and it lacks USB drivers. If anything, common software written in the C family for common software needs to run on top of Redox through a separate ports tree.
 
Interesting - care to explain which part was the performance bottleneck? The usual suspects like graphics are well optimized in Qt and things like IO can be substituted easily.
I can't pinpoint exactly what's the issue. Initializing around 3500 instances of QLabel, each showing a single emoji, was much too slow. On my (very old) desktop, it took 10 seconds or more, unacceptable to me. I eliminated unnecessary string conversions by storing everything as "QString literals", but this didn't help. I found workarounds (deferring this initialization), but had to add extra hacks (a timer-controlled delay of 1ms between initializations) because of the design of Qt's event handling, this was the only way to keep serving X11 events (so, otherwise the app appeared "frozen"). Code here.

With a full rewrite without Qt, I could initialize everything on startup taking just 3 seconds (or less, didn't measure exactly) on my old machine, although it's doing the same things that could be expensive: load the glyph with freetype, decode it with libpng, scale it to the required size (now custom code using a simple filter matrix) ... see here: https://github.com/Zirias/xmoji

I'm sure it's a nice exercise, but anything beyond simple "objects" needs a lot of boiler plate code to implement inheritance, vtables, type inference, etc. Not only in the implementation, but also when using it somewhere else.
It's more than just a "nice exercise". From what I see, a lot of code written in C these days uses "simple objects" (OO without polymorphism), and that's actually "straight forward" in C: use structs to describe your class members, some namespacing for the methods, opaque pointers as object handles, and for inheritance, just put the "parent" struct as the first member.

The cases where you really need polymorphism are rare (see also principles like "composition over inheritance"), and yes, doing that in C is a bit ugly and you should take some time to decide whether you want to do that or some other language would serve the purpose better. It's still manageable, for a suggestion see my code linked above. :cool:

The problem is managing scattered but interdependent state, with async calls from different ends (API, syscalls, interrupts), possibly multithreaded. You get that both in GUI frameworks and in OS kernels. The length of "Monster loops" is not a problem per se. But when locking and resource allocation are combined with C style error handling, they tend to have convoluted exit conditions, and / or exit paths when using goto.
I'm still sure this can all be done in "clean" code. No doubt it's quite some work to do. A lot of existing code would probably have to be redesigned and rewritten from scratch... 🤨

Good question, I'm more familiar with the unpleasant outcomes than the process that creates it. Still thinking about it, but my theory is that it's caused by the limited composability in C. You can basically delegate code execution and data manipulation to a function you call, that's it. What you cannot delegate is things like local control flow, type inference or resource and mutex management. At best you can put some of that into macros, but macros don't compose well either.
In my experience, you can structure your project in C almost in any way you like. But as soon as this requires something "generic" (like you want to factor out some algorithm that doesn't depend on exact types), the price you pay is interfaces with lots of void *. Not pretty, especially no guarantees by the compiler any more about the pointers passed around -- but manageable.
 
@essekaj I hope you continue to write code for FreeBSD.

It would be awesome if you'd make YouTube videos or blog posts about your coding expierence. Maybe - since you are fleeing your country - you have free time for that.
 
I can't pinpoint exactly what's the issue. Initializing around 3500 instances of QLabel, each showing a single emoji, was much too slow. On my (very old) desktop, it took 10 seconds or more, unacceptable to me. I eliminated unnecessary string conversions by storing everything as "QString literals", but this didn't help. I found workarounds (deferring this initialization), but had to add extra hacks (a timer-controlled delay of 1ms between initializations) because of the design of Qt's event handling, this was the only way to keep serving X11 events (so, otherwise the app appeared "frozen"). Code here.

I had a look, you're basically fighting Qt there by using widgets (QLabel) for what is essentially content. Widgets do dynamic placement and size negotiation, and 3500 signal -> slot connections is another problem that gets you into O(N^2) territory. The canonical way would be to use an item model and only render the visible parts. Although that may still be too slow, given your numbers on emoji rendering. But it isn't a Qt problem then, per se.

It's more than just a "nice exercise". From what I see, a lot of code written in C these days uses "simple objects" (OO without polymorphism), and that's actually "straight forward" in C: use structs to describe your class members, some namespacing for the methods, opaque pointers as object handles, and for inheritance, just put the "parent" struct as the first member.

The cases where you really need polymorphism are rare (see also principles like "composition over inheritance"), and yes, doing that in C is a bit ugly and you should take some time to decide whether you want to do that or some other language would serve the purpose better. It's still manageable, for a suggestion see my code linked above. :cool:

It's one thing to use it for your own limited application, and another to make it a user-extensible framework.

I'm still sure this can all be done in "clean" code. No doubt it's quite some work to do. A lot of existing code would probably have to be redesigned and rewritten from scratch... 🤨

In my experience, you can structure your project in C almost in any way you like. But as soon as this requires something "generic" (like you want to factor out some algorithm that doesn't depend on exact types), the price you pay is interfaces with lots of void *. Not pretty, especially no guarantees by the compiler any more about the pointers passed around -- but manageable.

Not what I meant. As explained there are hard limits to what you can express in C. But I suppose you have to encounter these situations yourself to acknowledge the limits.
 
I had a look, you're basically fighting Qt there by using widgets (QLabel) for what is essentially content. Widgets do dynamic placement and size negotiation, and 3500 signal -> slot connections is another problem that gets you into O(N^2) territory. The canonical way would be to use an item model and only render the visible parts. Although that may still be too slow, given your numbers on emoji rendering. But it isn't a Qt problem then, per se.
I don't think this is correct. Sure, you could argue from the design perspective: we have models that are just "lists of emojis", most of them read-only, and we have "floating" views to display them, all read-only. Qt's model/view classes would be a match. But things like negotiating sizes and positions must be done anyways, it's now the job of "item delegates" instead of something derived from QWidget. Only doing that for the part that's currently visible isn't a good solution either. When the UI still allows having hundreds of emojis on screen at the same time, this will result in a lot of work when switching to a different tab. Although I never tried the model/view approach here (it's just a LOT more work, there's no view available for a floating layout of items, and adding a custom widget layout was much more straight-forward), I did some experiments trying the same (only initialize/render what's visible). As the event loop is blocked, unless you're doing dirty hacks with timers that just add a lot more overhead, even a delay of 500ms when switching tabs is very noticeable to the user and not acceptable to me. Possibly an implementation of drawing in an "item delegate" adds less overhead than QWidget does, but the problem itself remains...

In my custom code (without a toolkit, although this of course means there's a "builtin" toolkit now), all the emojis are widgets as well, so of course negotiating size/position is also part of the initialization of the UI, still it performs a lot better. Good enough for "eager" initialization on startup, so e.g. switching a tab or scrolling happens "instantly" regardless of how weak your desktop might be. That's expected, as on the one hand, I don't need portability to e.g. win32/gdi or wayland, so I can use XRender (X11) instead of e.g. cairo, and on the other hand, my widget abstraction only needs to implement what's specifically needed for this application.

It's one thing to use it for your own limited application, and another to make it a user-extensible framework.
Actually, my implementation here is generic, even the "type registry" works dynamically. It requires a certain amount of "boilerplate" and following conventions in each implementation of a class of course. But IMHO much more important, again: Most of the time, you should use very little inheritance anyways and just don't need polymorphism, and in these cases, implementations in C are straight forward, not requiring any "framework" at all.

Not what I meant. As explained there are hard limits to what you can express in C. But I suppose you have to encounter these situations yourself to acknowledge the limits.
I think it is what you meant, but I forgot to describe a mental step here: Whatever you "compose", in the end, what you delegate to a separate module is functionality, which can always map to plain functions (and references/pointers to these) if you want, but it can require adding abstractions for "generic" types. The latter is what C can't give you, short of void *. Which doesn't make it impossible, but it's certainly ugly and very much limits what the compiler can do to help you ensuring correct code.
 
Back
Top