So I'm here because, well, there are too many people around there saying, hey, you should rewrite
your project into Rust.
And I am maintaining a project in C, which has like 25 years old, and I am saying no, and
now I'm going to say why, or maybe why I'm not going to rewrite my project to Rust.
I am not going to be as funny as the one before me, it's impossible.
So if you are expecting something better, just you can go away, there is nothing to see here.
Anyway, if you are not scared, yeah, the quest is simple.
We have many years old C code base, which was written in like 1998, 99 in ISOC, and so
we are expecting to keep this because it runs and rewriting this completely through
something else, means like imposing more bugs than fixing.
Yes, we do have memory safety concerns, there are lots of people actually saying,
anything written in C is bad because it's memory unsafe, and I am saying, well, it depends.
If it doesn't crash for like last 10 years, I am asking you where is it unsafe.
On the other hand, we are improving its ever since, and in the new code there are some problems,
and we are fixing them continuously, but it's still a piece of software, which is rather stable.
So I am asking why should I rewrite something to Rust when it's not broken?
So the requirements are, I want to keep what works.
I don't want to rewrite a whole parser written in C, which has been working for last 25 years.
I want to automate most of the refactoring I am going to do to make the future updates safe.
I want to lead the developers, the way they should code, it's obvious that you can shoot
your own leg in the most creative place in C, and I am not going to make you know do it.
I am just going to make it harder for you.
I want to allow all the food guns you want to have, but it will be obvious that you are shooting your leg.
And then on the code review, I'm going to look at it and say, no, that's not going there.
So as I am saying here, it should be hard to write bad code, but I don't like making it impossible
because making the bad code impossible throws out even some good code.
So what I'm expecting to happen with on safe code is, ideally, it's obvious, it built error.
It's what Rust has.
Or there can be aesthetic analyzer error, which is something you can just put into your CI,
and then you say, well, it's killed by the aesthetic analyzer where I'm not going to,
I'm not going to include your updates.
It can fail on some unit test error, it can just stick out.
If I see a typecast in a plane side in your changes, I say no, because you should not do typecast,
but more on that later.
And one of those requirements is if some code is intended to be innocent,
it must actually be innocent, which is a reason why I'm not going to rewrite it into C++.
Because I don't want to study what's the plus happening, whether it's overloaded and where.
So innocent codes, the code which looks innocent must also be innocent.
Now the controversial thing, how to do it?
The as a part is what should be the default?
Basically, if you do something, do it locally.
If you can do it locally, don't rely on anything which is global.
You should put const everywhere.
This is something which Rust has done it, it has done well.
Doing it, view posit, doing it making everything mutable, marked as mutable,
and everything const is just const by default.
There are other things like functions that are not pure.
I am not against things like side effects.
These are legitimate, but it should have a reason.
What I hate actually is like returning things in the arguments.
And so, because it's also obscures what you are doing,
but what I am trying to say, the code should say what it's doing.
And if you try to write a code which shows what you are doing,
then you should be safe.
If you are writing a code which is adhering to specific rules,
it's just adhering to specific rules.
And the last point here, no void pointers anywhere.
If you see any void pointer, just replace it by something else.
There is no single reason to use void pointers.
Apart from things like the return value from the memory allocator.
If you are allocating memory, yes, you get a void pointer that's okay.
But you should not put a void pointer as an argument
of some object of some size, not at all never.
But first we have to read the get rid of globals.
It's typically a context.
You don't have a reason to look at a global variable,
which has how you should, for example, format time.
It's typically a context.
Or it's a global information.
It's something like how the page size was the page size.
Well, yes, then it's a read only global information,
and you are not writing it.
Well, yes, then there is a really shared data,
and you probably have to do some log access.
It should be explicit more on that later.
This is my favorite part.
You should not use void pointers.
And I will say it several times again.
You can use unions.
Unions are good.
Unions are fine.
And you can, with now, with C11 and 18 and 20 C or 4,
or what's that now, you can use anonymous structures
inside unions, inside structures.
Well, yes, it looks ugly for the beginning.
But you can use a structure, put there the type of what piece
of the union is there, and you can use a macro.
And if you are against macros, don't try it in C.
C is a language with macros.
And if the macro preprocessor, if the C preprocessor
is not enough for you, hello M4.
Is there anybody who has written some M4 code in like 10 years?
Yes, I have written like a thousand lines of M4,
and it has a big thick warning of the beginning.
Do not read this code.
Do not continue after this line.
Until you want to get brain damage.
And I am not kidding.
It actually is in the code.
You can Google it.
You can find it.
You can have generated code.
You can have a linked list type for every single thing.
You want to put into the linked list.
So if you have one structure and these structures
are put into the linked list, you have a linked list
of these structures as a type.
And then you have another structure and you put
that structure into a linked list.
You have a linked list of that structure as another type.
You have a complete set of functions,
manipulating these data structures for every single data type.
Yes, it is greedy.
But we are in the times where the compiler time
is actually much cheaper than the time you spend
debugging these problems.
So please make the compiler do what the compiler should do.
And yes, this is kind of abusing the type system.
Please do it.
This is one of those things she can do.
And please do not typecast plainly anywhere for any reason.
If you want to typecast, you have to write the macro.
And the macro says what you are intending with doing it
and not only that the macro should actually check
that what you put in is intended.
It is possible.
And I don't have it in this presentation, but I have it in the code.
It is possible to check that the specific points you are putting in
is actually of the type you are expecting to be put in.
Yes, it is possible to write type checking macros.
And if it makes sense, you should do it.
There are other things one should do like acquiring
managing the local acquired resources.
Well, you acquire anything and then you return.
Well, no, please.
It should be the explicit releasing.
Make some problems.
So you can have some clean up hooks.
It is not a new feature.
It is a thing not yet standardized in C because it
it got in some very nasty problems inside the committee.
But every component is okay with clean up hooks,
which are executed when a variable gets out of scope.
You can have end of task hooks.
Who is that are run just before you enter the poll again?
You can have different times for different allocation
scopes and you just have to move the data from the local
erochatory source to the global erochatory source,
either basically by explicitly copying,
to make it explicit that now you are writing something global.
And yes, you should mark stack and stack all
and elochatory data is the worst one.
At least please mark them.
I don't see much, much of better things.
Yeah, this is some example.
This is how one can use an unlock macro.
This basically is a function, it's a dummy function.
It gets a, it's got a size from some table.
And the table has its lock inside.
So the get the information you have to lock.
So there is a macro doing some some weird things
with the locking, what's the thing to look at is the return
inside the locking and the return is safe.
You can return from the locked context because the lock
is released automatically.
So this means you can just let it go.
The implementation is quite scary.
One has to look through it and read through it.
It takes some time in the beginning.
When one gets to actually the grasp of what is is doing,
yes, then you can use it and you can be sure that it works.
And there is, yes, this is one part, which basically
you peruse is, abuse is a force cycle to do the block,
to do the block c-mentics.
And there is also the cleaner function which is called
when the block is left.
And this is basically, you can see and here it is doing the
object lock simple.
And the unlocking is actually done in the cleaner function.
And this is typically put in one place together in some
header file.
Yes, then there is some memory allocation strategies.
You should use what fits your project.
Do not be bad to me.
In a bird, what we are using, yeah, I have not said not
said that, but I'll return to that.
In a bird, we are using hierarchical pools.
And the pools are keeping track of everything.
So if you walk from a root place, you can traverse all the
allocated memory and show what's located and what's
where.
There is a temporary allocation.
It just gets freed at the task.
You do the MPL lock and then it gets freed.
You don't have to worry.
You can also get temporarily some global resources.
And it's the same principle.
You reference it and immediately schedule a release task
to be done at the end of the task.
So you know, it's safe to store.
It's not safe to, yeah, I was skip, skip, skip.
Yes, you can see it in a bird.
You can see it in a lip UCV.
This is myself.
Thank you.