With e.g. std::ofstream, you are wrapping a handle, such as a POSIX file descriptor. So that part of your description is accurate.
However, this is different from what happens with OpenGL. The problem is that if you wrap an OpenGL texture (which is a handle), you end up with some problems because you can only free it with the correct context active, and it might become free for other reasons besides the wrapper being destroyed. So you are no longer wrapping a handle (like std::ofstream), but you are actually creating a new handle-to-handle, and wrapping that, and explicitly managing the lifetime OpenGL resources with a separate object somewhere else.
Of course, you could just wrap an OpenGL texture, explicitly decide not to handle context invalidation, and then be careful to ensure that your objects get destroyed with the OpenGL context active. You lose some flexibility and you have to babysit RAII to be sure it "does the right thing".
So what I'm saying here is that OpenGL textures are not like file handles.
The whole discussion here is about how these things differ in subtle ways. The problem with “squinting” at the problem is you end up e.g. using RAII and then having to redesign your system because RAII doesn’t match, and the problems weren’t obvious when you started out.
Not to make too fine a point about it here, but file handles are perfect matches for RAII / Rust lifetimes, unlike OpenGL textures. You just close the handle at end of object lifetime. You can get errors when you close but current recommendation is to ignore errors when cleaning up and have a separate path for closing/committing data on close when writing, so on most paths the destructor will nop. This works well.
An example of a mismatch with the language semantics and file handles is with GC. If you close a file handle in a C++ destructor or Rust drop(), it’s fine, those are run deterministically. If you use a JVM finalizer to close your file handle that might not happen soon enough (cue EMFILE).
Again, the reason why textures don’t work this way is because you have to release them in a certain context and they might be released outside your control.
So my example might not have been the best, because usually, they have a closely matching lifecycle, but the greater point that I was trying to make is that managing handles of handles (of handles) is not weird if you have to do some impedance matching. Maybe file handles are often straightforward, and therefore only require one "additional layer of handle indirection", but there's probably other good examples like say, threads in thread pools, or a whole bunch of stuff that's going on inside an OS kernel, especially a multiprocessing one.
I was only attempting to make that point that wrapping handles with other handles, rather than being silly, is a pretty good pattern to address this problem, and it shows up all over.
The file handle thing I already pointed out; there are better examples.
Agreed, something like an FD matches RAII very well, but you can open one file multiple times, dup fds and multiple processes can do all this. So the FDs end up mapping in some non-trivial, probably refcounted way to other objects in the kernel.
So it seems with OpenGL, this kind of mapping / bookkeeping layer gets hoisted up into the application framework -- which might be a reasonable choice.
I wonder if this is where Rust can actually help. I.e. could the lifetime tracking in the language allow you to construct a library where the unref was guaranteed to happen only when the right context is active?
And if such a library could be constructed, what would the loss of flexibility cost?