Earlier quoted context omitted.
And the consequence is that, at least in C++, we don't see the benefit you describe of "objects can never be in an uninitialized or partially-initialized state". Anyway, I think this could be fixed, if we wanted to. C just describes the objects as being uninitialized and has a bunch of UB around uninitialized objects. Nothing in C says that an implementation can't make every uninitialized object 0. As such, it would…
It's possible to fix this in application code with a Primitive or NoDefault wrapper that acts like a T, except doesn't have a default constructor. Use Primitive wherever you'd use int that it matters (e.g. struct fields), and leaving it uninitialized will be a compiler error.
Defer available in gcc and clang
211–220 of 262 posts
Re: Defer available in gcc and clang
#212I have a personal aversion to defer as a language feature. Some of this is aesthetic. I prefer code to be linear, which is to say that instructions appear in the order that they are evaluated. Further, the presence of defer almost always implies that there are resources that can leak silently. I also dislike RAII because it often makes it difficult to reason about when destructors are run and also admits accidental l…
Indeed, `defer` as a language feature is an anti-pattern.
It does not allow the abstraction of initialization/de-initialization routines and encapsulating their execution within the resources, transferring the responsibility to manually perform the release or de-initialization to the users of the resources - for each use of the resource.
> I also dislike RAII because it often makes it difficult to reason about when destructors are run [..]
RAII is a way to abstract initialization, it says nothing about where a resource is initialized.
When combined with stack allocation, now you have something that gives you precise points of construction/destruction.
The same can be said about heap allocation in some sense, though this tends to be more manual and could also involve some dynamic component (ie, a tracing collector).
> [..] and also admits accidental leaks just like defer does.
RAII is not memory management, it's an initialization discipline.
> [..] what I would want is essentially a linear type system in the compiler that allows one to annotate data structures that require cleanup and errors if any possible branches fail to execute the cleanup. This has the benefit of making cleanup explicit while also guaranteeing that it happens.
Why would you want to replicate the same cleanup procedure for a certain resource throughout the code-base, instead of abstracting it in the resource itself?
Abstraction and explicitness can co-exist. One does not rule out the other.
Re: Defer available in gcc and clang
#213Earlier quoted context omitted.
> No. We were talking about using malloc/free vs. a resizable buffer. No. This is a conversation about Go. My example[1], that you responded to, was an example taken from a real-world project I've worked on which uses Go wrappers around SDL functions to render text. Nowhere did I mention malloc or free, you brought those up. The code you gave this time is literally my first example (again, [1]), which allocates a new…
I invite you to read the code again. You missed a few things. Notably it uses a shared memory buffer, as discussed, and does free it upon defer being executed. It is essentially equivalent to the second C snippet above, while your original example is essentially equivalent to the first C snippet.
To anyone used to SDL, your proposed API is extremely surprising.
It would've made your point clearer if you'd explained this coupling between SDL_Renderer and text rendering in your original post.
But yes, I concede that if there was any reason to do so, putting a scratch surface into your SDL_Renderer that you can auto-resize and render text to would be a solution that makes for slightly nicer API design. Your SDL_Renderer now needs to be passed around as a parameter to stuff which only ought to need to concern itself with CPU rendering, and you now need to deal with mutexes if you have multiple goroutines rendering text, but those would've been alright trade-offs -- again, if there was a reason to do so. But there's not; the allocation is fast and the text rendering is slow.
Re: Defer available in gcc and clang
#214Earlier quoted context omitted.
I wonder what the thought process of the Go designers was when coming up with that approach. Function scope is rarely what a user needs, has major pitfalls, and is more complex to implement in the compiler (need to append to an unbounded list).
> I wonder what the thought process of the Go designers was when coming up with that approach. Sometimes we need block scoped cleanup, other times we need the function one. You can turn the function scoped defer into a block scoped defer in a function literal. AFAICT, you cannot turn a block scoped defer into the function one. So I think the choice was obvious - go with the more general(izable) variant. Picking the a…
You kinda-sorta can by creating an array/vector/slice/etc. of thunks (?) in the outer scope and then `defer`ing iterating through/invoking those.
Re: Defer available in gcc and clang
#215Earlier quoted context omitted.
I invite you to read the code again. You missed a few things. Notably it uses a shared memory buffer, as discussed, and does free it upon defer being executed. It is essentially equivalent to the second C snippet above, while your original example is essentially equivalent to the first C snippet.
Wait, so your wrapper around SDL_Renderer now also inexplicably contains a scratch buffer? I guess that explains why you put RenderTextToSurface on your SDL_Renderer wrapper, but ... that's some really weird API design. Why does the SDL_Renderer wrapper know how to use SDL_TTF or PangoCairo to draw text to a surface? Why does SDL_Renderer then own the resulting surface? To anyone used to SDL, your proposed API is ext…
Aside from my failure in dealing with the hardest problem in computer science, how would you improve the intent of the API? It is clearly improved over the original version, but we would do well to iterate towards something even better.
Re: Defer available in gcc and clang
#216Earlier quoted context omitted.
What C++ really needs is an automated way to handle exceptions in destructors, similar to how Java does in its try-with-resources finally blocks.
While not automated, you can make use of function-try-blocks, e.g.: struct Example { Example() = default; ~Example() try { // elease resources for this instance } catch (...) { // take care of what went wrong in the whole destructor call chain } }; -- https://cpp.godbolt.org/z/55oMarbqY Now with C++26 reflection, one could eventually generate such boilerplate.
The mechanism in Java I was alluding to is really the Throwable::addSuppressed method; it isn’t tied to the use of a try-block. Since Java doesn’t have destructors, it’s just that the try-with-resources statement is the canonical example of taking advantage of that mechanism.
Re: Defer available in gcc and clang
#217Earlier quoted context omitted.
Wait, so your wrapper around SDL_Renderer now also inexplicably contains a scratch buffer? I guess that explains why you put RenderTextToSurface on your SDL_Renderer wrapper, but ... that's some really weird API design. Why does the SDL_Renderer wrapper know how to use SDL_TTF or PangoCairo to draw text to a surface? Why does SDL_Renderer then own the resulting surface? To anyone used to SDL, your proposed API is ext…
You're right to call out that the SDLRenderer name was a poor choice. SDL is an implementation detail that should be completely hidden from the user of the API. That it may or may not use SDL under the hood is irrelevant to the user of the API. If the user wanted to use SDL, they would do so directly. The whole point of this kind of abstraction, of course, is to decouple of the dependence on something like SDL. Point…
Re: Defer available in gcc and clang
#218Earlier quoted context omitted.
I feel like C people, out of anyone, should respect the code gen wins of defer. Why would you rely on runtime conditional branches for everything you want cleaned up, when you can statically determine what cleanup functions need to be called? In any case, the biggest advantage IMO is that resource acquisition and cleanup are next to each other. My brain understands the code better when I see "this is how the resource…
Defer might be better than nothing, but it's still a poor solution. An obvious example of a better, structural solution is C#'s `using` blocks. using (var resource = acquire()) { } // implicit resource.Dispose(); While we don't have the same simplicity in C because we don't use this "disposable" pattern, we could still perhaps learn something from syntax and use a secondary block to have scoped defers. Something like…
using (var resource1 = acquire() {
using (var resource2 = acquire()) {
using (var resource3 = acquire()) {
// use resources here..
}
}
}
Compared to: var resource1 = acquire();
defer { release(resource1); }
var resource2 = acquire();
defer { release(resource2); }
var resource3 = acquire();
defer { release(resource3); }
// use resources here
Of course if you want the extra scopes (for whatever reason), you can still do that with defer, you're just not forced to.Re: Defer available in gcc and clang
#219Earlier quoted context omitted.
You're right to call out that the SDLRenderer name was a poor choice. SDL is an implementation detail that should be completely hidden from the user of the API. That it may or may not use SDL under the hood is irrelevant to the user of the API. If the user wanted to use SDL, they would do so directly. The whole point of this kind of abstraction, of course, is to decouple of the dependence on something like SDL. Point…
I think the most obvious improvement would be: just make it a free function which returns a surface, text rendering is slow and allocation is fast
Re: Defer available in gcc and clang
#220Earlier quoted context omitted.
I think the most obvious improvement would be: just make it a free function which returns a surface, text rendering is slow and allocation is fast
That is a good point. If text rendering is slow, why are you not doing it in parallel? This is what 9rx called out earlier.
But that 0.1 millisecond to render a string is an eternity compared to the time it takes to allocate some memory, which might be on the order of single digit microseconds. Saving a microsecond from a process which takes 0.1 milliseconds isn't noticeable.