I wrote nearly all of https://apps.apple.com/us/app/two-birds-one-stone/id15396463... on a train without internet. It was about a half hour journey, and I found that in such a short amount of time, I would set a small goal and work in a very distraction free way to achieve it. It was very good for doing small things, but sometimes doing larger things (like big refactors) is a bit more difficult. On occasion, I would also dedicate a train ride to just writing up a todo list.
I gave myself the rule of no internet while on the train, so sometimes I would just accumulate a list of questions I wanted to answer later.
There is definitely something to it, and you can get heaps done, but it needs to be supported with some non-train time (e.g. for me, it was all the app store stuff, debugging with real hardware, etc)
Not really a shell one liner, but ctrl+r (a readline command to do an incremental search backwards through history) is something that has been present on every shell I've used for decades without realising it, One day I decided to take the time to read all the magic readline commands because I wanted a way to quickly edit the N-th argument of a command with lots of arguments, and there were way too many of them. There were so many commands that I had no hope of remembering them all, but I figured I could just remember a few useful ones - and ctrl+r was one of them (ctrl+w and alt+b were the other two)
More to the letter of the question, I use "cd -" frequently, "ps -e | grep some_process_i_would_like_the_pid_for", and while I don't use it frequently, I didn't know about "ssh-copy-id" for a long time, and would do it manually with a text editor in the past. Sorry if they are not sufficiently fancy - but for things to get used day to day for me, they will need to be short and sweet.
I also same here. Has since I discovered Ctrl+R, and equipped it with fzf (https://github.com/junegunn/fzf), every terminal command is in my hand, I can fuzzy search and not need to remember the exact command. This really saved me a lot of times.
`cd -` is great for when you just want to nip out of your current directory for a second and then come straight back - especially as I'm pretty sure that most of us never think about pushd/popd until after we've moved to the other directory with cd :)
Semi-related: if I want to do a little side quest, maybe pop over to another repo or git worktree for a quick look, I'll drop into a subshell just by running bash before cd-ing. Then I just exit that shell (alias q=exit) when I want to return back to the original dir and context.
You know that you can just press <CTRL+D> at the bash prompt to quit it as well? (In fact <CTRL+D> will quit out of most command line programs as it's the End Of Transmission character)
No need to be fancy. These are super practical! ctrl+r is a lifesaver once it clicks, and cd - is such a small thing that makes moving around so much smoother.
Perhaps I am wrong, but the videos on this channel seem to have scripts that seem like they are very much AI written.
I watched this video a week or so ago, and a few more on the channel because they were promising to answer other interesting questions (interesting for me at least).
However, it seemed like there was something off with all the content. The best explanation I have for it is that it is AI generated scripts (I recall a real lack of coherence in the video about which side you put the fuel door on a car), so I just stopped watching.
I'd be curious if anyone else has a similar feeling or a better way of expressing it, because I am not feeling very eloquent here.
I've no inclination to click through to the video, but there are undoubtedly many youtube channels that use AI generated scripts spoken by AI generated voices in order to try and make a quick buck, following the advice of many "make-money-fast" videos on youtube, a substantial portion of which, no doubt, are themselves read by AI voices from AI generated scripts.
Whenever I encounter one in my recommends I use the "do not recommend from this channel" option, but I'm clearly fighting a losing battle.
Just one of the many reasons I think AI generated content should be required by law to be marked as such, and passing it off as human produced should be proscribed. When we drown in a sea of slop at least it will be clearly labeled.
If you just want to write C and are ok linking to stuff that is in other languages, then I used SDL2 for my game which I wrote in C and is on the app store. It was a very pleasant experience, and if I were doing an iOS thing again, I would likely do something similar.
I think I had one objective C file in there for calling a library function I needed, but otherwise I just wrote C code.
The game was 2d, and I just used the stuff in SDL_render.h for doing the graphics.
SDL2 has input stuff built in, so that is easy.
I didn't need networking for my game, so I didn't have it, but networking is not particularly difficult to do cross platform (I'm assuming iOS lets you just use the "normal" sockets API?)
I also used SDL2 for the sound stuff, which was mostly playing back prerecorded things, but also a little bit of stuff synthesized on the fly.
That doesn't follow? If you replaced every call of memcpy with memmove (to use an example from the standard library), then your program is no less correct than it was before (and possibly more). The converse is that adding "restrict" qualifiers to existing functions can only make your program less correct, not more.
In this context it follows. If you believe the pointers dont alias (and want to use simd) then there should be no data-dependencies inbetween indices.
(If there are data-dependencies it is quite likely that you have designed your algorithm to accomodate that, i.e. make it suitable for vectorised access. parallel prefix-sum would be an example of this).
And without data-dependencies vectorized code is exactly equal to unvectorized code in all circumstances.
If however, you do have data-dependencies, then your algorithm is wrong.
Vectorizing can surface this error, while keeping it unvectorized may hide it.
In my previous example (parallel prefix-sum) if you had a direct data-dependency to the previous index your program would've been wrong - regardless of vectorization.
In short, while your statement is true in general, I believe that it is not applicable in the context of the discussion.
You will be pleased to know that you are not the only one who does this.
I previously went down the rabbit hole of fancy unit test frameworks, and after a while I realised that they didn't really win much and settled on something almost identical to what you have (my PRINT_RUN macro has a different name, and requires the () to be passed in - and I only ever write it if the time to run all the tests is more than a second or so, just to make it really convenient to point the finger of blame).
The thing that I do which are potentially looked upon poorly by other people are:
1) I will happily #include a .c file that is being unit tested so I can call static functions in it (I will only #include a single .c file)
2) I do a tiny preprocessor dance before I #include <assert.h> to make sure NDEBUG is not defined (in case someone builds in a "release mode" which disables asserts)
This test/src separation always felt like html/css to me. When still using C, I wrote tests right after a function as a static function with “test_” in the name. And one big run-all-tests function at the end. All you have to do then is to include a c file and call this function. Why would I ever want to separate a test from its subject is a puzzling thought. Would be nice to have “testing {}” sections in other languages too, but in C you can get away with DCE, worst case #ifdef TESTING.
Because tests also serve as api validations. If you can't write a test for functionality without fiddling with internal details the api is probably flawed. Separation forces access via the api.
I don’t need anything to be “forced” on me, especially when this forcing is nominal and I can #include implementation details. You may need that in teams with absurdly inattentive or stubborn members, but for you-personally it’s enough to get the principle and decide when to follow it. The idea is to simply keep tests close to the definitions because that’s where the breaking changes happen.
If you can't write a test for functionality without fiddling with internal details the api is probably flawed
This logic is flawed. If you have an isolated implementation for some procedure that your api invokes in multiple places (or simply abstracted it out for clarity and SoC), it’s perfectly reasonable to test it separately even if it isn’t officially public.
I agree with all of the above. The only fancy thing which I added is a work queue with multiple threads. There really isn't any pressing need for it since natively compiled tests are very fast anyway, but I'm addicted to optimizing build times.
Header files are one of the things I miss the most about languages that aren't C. Having a very clear distinction between public and private, and interface and implementation is one of my favourite things about C code (at least the way I write it).
Being able to just read through a library's .h files to know how to use it is really nice. Typically, my .h files don't really look like my .c files because all the documentation for how to use the thing lives in the .h file (and isn't duplicated in the .c file). It would be entirely possible to put this documentation into the .c file, but it makes reading the interface much less pleasant for someone using it.
> Header files are one of the things I miss the most about languages that aren't C. Having a very clear distinction between public and private, and interface and implementation is one of my favourite things about C code (at least the way I write it).
I always found this argument baffling, because the way some other language solve this problem is with tooling, which is a much better way to do it in my opinion.
Take Rust for example. You want to see the interface of a given library and see how to use it? Easy. Type in `cargo doc --open` and you're done. You get a nice interface with fully searchable API interface with the whole public API, and it's all automatic, and you don't have to manually maintain it nor have to duplicate code between your header and your source file.
This is probably something where it comes down to preference and familiarity. I would much prefer a simple text file for documentation that I can grep, open in my text editor, modify easily without switching context (oh, I should have been more explicit in the documentation I wrote - let me just fix that now), etc. All the features you mentioned "nice interface, fully searchable API interface, whole public API" are exactly what you get if you open a well written header file in any old text editor.
I used to be a big fan of doxygen etc, but for the stuff I've worked on, I've found that "pretty" documentation is way less important than "useful" documentation, and that the reformatting done by these tools tends to lead towards worse documentation with the people I have worked with ("Oh, I need to make sure every function argument has documentation, so I will just reword the name of the argument"). Since moving away from doxygen I have stopped seeing this behaviour from people - I haven't tried to get a really good explanation as to why, but the quality of documentation has definitely improved, and my (unproven) theory is that keeping the presentation as plain as possible means that the focus turns to the content.
I don't know if rust doc suffers the same issues, but the tooling you are mentioning just seems to add an extra step (depending on how you count steps I suppose, you could perhaps say it is the same number of steps...) and provide no obvious benefit to me (and it does provide the obvious downside that it is harder to edit documentation when you are reading it in the form you are suggesting).
But with all these things, different projects and teams and problem domains will probably tend towards having things that work better or worse.
The problem with this is no one agrees on the definition of "well-written", so consistency is a constant battle and struggle. Language tooling is a better answer for quality of life.
That's an interesting assertion, but not one that matches the experience I've had.
It is one of those things that sounds "obviously true", but in practice I've found that it doesn't really live up to the promise. As a concrete example of this, having a plain text header file as documentation tends to mean that when people are reading it, if they spot a mistake or see that something isn't documented that should be documented, they are much more likely to fix it than if the documentation is displayed in a "prettier" form like HTML.
The problem with header files that aren't "well-written" tends to be that the actual content you are looking for isn't in there, and no amount of language tooling can actually fix that (and can be an impediment towards fixing it).
I have the same experience a lot of the time with 3rd party rust crates. Doc.rs is amazing - but it’s rare that I’ll use a library without, at some point, hitting view source.
for most rust ive done (not tons) the docs were very basic as onky auto generated with minimal content. totally useless, have to read sources to find out what is in there.
auto documentation to me ia just ti satisfy people who need to tick all of these boxes and want to do with minimal effort. has dox has tests etc. such artitude never leads to quality.
Useful documentation is impossible for the developer to write - they are too close to the code and so don't understand what the users (either api users or end users) need to know. Developers agonize over details that users don't care about while ignoring as obvious important things users don't know.
I know people look at me like I’m a heathen and a scoundrel, but I think a lot of software teams spend too much time trying to make things consistent. Where’s the ROI? There is none.
GitHub readmes? Bring on the weird quirks, art, rants about other software, and so on. I’ll take it all.
Don’t get me started on linters. Yes, there’s lots of things that should actually be consistent in a codebase (like indentation). But for every useful check, linters have 100 random pointless things they complain about. Oh, you used a ternary statement? Boo hoo! Oh, my JavaScript has a mix of semicolons and non semicolons? Who cares? The birds are singing. Don’t bother me with this shite.
Software is a creative discipline. Bland software reflects a bland mind.
> Oh, my JavaScript has a mix of semicolons and non semicolons? Who cares?
i had to refactor and port a javascript codebase that contained a mix of all of javascripts syntactic sugar, no comments anywhere in the codebase, and i was unable to ask the original devs any questions. the high amount of syntactic sugar gave me "javascript diabetes" - it was fun figuring out all the randomness, but it delayed the project and has made it extremely difficult to onboard new folks to the team after i completed the port.
painting is a creative discipline, and the mona lisa has stood the test of time because davinci used a painting style and materials that set the painting up for long term use.
a codebase without standards is akin to drawing the mona lisa on a sidewalk with sidewalk chalk.
I use a .git-blame-ignore-revs file. So if you run the fix once, dump that commit in the file and use it when you use git blame, it'll exclude blame in that commit.
The historical way is to have a .ml and a .mli files. The .ml file contains the implementation. Any documentation in that file is considered implementation detail, will not be published by ocamldoc. The .mli file contains everything users need to know, including documentation, function signatures, etc.
Interestingly, the .mli and the .ml signatures do not necessarily need to agree. For instance, a global variable in the .ml does not need to be published in the .mli. More interestingly, a generic function in the .ml does not need to be exposed as generic in the .mli, or can have more restrictions.
You could easily emulate this in Rust, but it's not the standard.
> and that the reformatting done by these tools tends to lead towards worse documentation with the people I have worked with ("Oh, I need to make sure every function argument has documentation, so I will just reword the name of the argument")
That seems like an orthogonal issue to me. I've seen places where documentation is only in the source code, no generated web pages, but there is a policy or even just soft expectation to document every parameter, even if it doesn't dd anything. And I've also seen places that make heavy use of these tools that doesn't have any such expectation.
> All the features you mentioned "nice interface, fully searchable API interface, whole public API" are exactly what you get if you open a well written header file in any old text editor.
No, you can't, and it's not even close.
You have a header file that's 2000 lines of code, and you have a function which uses type X. You want to see the definition of type X. How do you quickly jump to its definition with your "any old text editor"? You try to grep for it in the header? What if that identifier is used 30 times in that file? Now you have to go through all of other 29 uses and hunt for the definition. What if it's from another header file? What if the type X is from another library altogether? Now you need to manually grep through a bunch of other header files and potentially other libraries, and due to C's include system you often can't even be sure where you need to grep on the filesystem.
Anyway, take a look at the docs for one of the most popular Rust crates:
The experience going through these docs (once you get used to it) is night and day compared to just reading header files. Everything is cross linked so you can easily cross-reference types. You can easily hide the docs if you just want to see the prototypes (click on the "Summary" button). You can easily see the implementation of a given function (click on "source" next to the prototype). You can search through the whole public API. If you click on a type from another library it will automatically show you docs for that library. You have usage examples (*which are automatically unit tested so they're guaranteed to be correct*!). You can find non-obvious relationships between types that you wouldn't get just by reading the source code where the thing is defined (e.g. all implementations of a given trait are listed, which are usually scattered across the codebase).
> I don't know if rust doc suffers the same issues, but the tooling you are mentioning just seems to add an extra step (depending on how you count steps I suppose, you could perhaps say it is the same number of steps...) and provide no obvious benefit to me (and it does provide the obvious downside that it is harder to edit documentation when you are reading it in the form you are suggesting).
Why would I want to edit the documentation of an external library I'm consuming when I'm reading it? And even if I do then the effort to make a PR changing those docs pales in comparison to the effort it takes to open the original source code with the docs and edit it.
Or did you mean editing the docs for my code? In that case I can also easily do it, because docs are part of my source files and are maintained alongside the implementation. If I change the implementation I have docs right there in the same file and I can easily edit them. Having to open the header file and hunt for the declaration to edit the docs "just seems to add an extra step" and "and provide no obvious benefit to me", if I may use your words. (:
Thanks for the constructive example of the rust doc.
I am not making things up when I say that the very first question I had about how to use this module, either is not answered, or I couldn't find the answer. That question was "what regular expression syntax is supported?". This is such a fundamental question, yet there is no answer provided.
As a preference thing, I don't really like examples in APIs (it is supposed to be a reference in my opinion) and I find them to be mostly noise.
> Why would I want to edit the documentation of an external library
I'm consuming when I'm reading it? And even if I do then the effort
to make a PR changing those docs pales in comparison to the effort
it takes to open the original source code with the docs and edit it.
Right, this is possibly where our experiences differ. I'm frequently pulling in loads of code, some of which I've written, some of which other people have written, and when I pull in code to a project I take ownership of it. Doesn't matter who wrote it - if it is in my project, then I'm going to make sure it is up to the standards I expect. A lot of the time, the code is stuff I've written anyway, which means that when I come back in a few months time and go to use it, I find that things that seemed obvious at the time might not be so obvious, and a simple comment can completely fix it. Sometimes it is a comment and a code change ("wouldn't it be nice if this function handled edge case X nicely? I'll just go in there and fix it").
The distinction between external and internal that you have looks pretty different to me, and that could just be why we have different opinions.
The parent linked to a subsection showing usage for a particular object. If you click back into the root level for the document there is a header specifying ‘syntax’, and other more ‘package-level’ documentation
> I am not making things up when I say that the very first question I had about how to use this module, either is not answered, or I couldn't find the answer. That question was "what regular expression syntax is supported?". This is such a fundamental question, yet there is no answer provided.
This is a fair question to have. As others have already said, this is the API reference for a particular class, so you won't get the high level details here. You can click in the upper left corner to go to the high level docs for the whole library.
> The distinction between external and internal that you have looks pretty different to me, and that could just be why we have different opinions.
Well, there are two "external" vs "internal" distinctions I make:
1. Code I maintain, vs code that I pull in as an external dependency from somewhere else (to give an example, something like libpng, zlib, etc.). So if I want to fix something in the external dependency I make a pull request to the original project. Here I need to clone the original project, find the appropriate files to edit, edit them, make sure it compiles, make sure the tests pass, make a PR, etc. Having the header file immediately editable doesn't net me anything here because I'm not going to edit the original header files to make the change (which are either installed globally on my system, or maintained by my package manager somewhere deep under my /home/).
2. Code that is part of my current project, vs code that is a library that I reuse from another of my projects. These are both "internal" in a sense that I maintain them, but to my current project those are "external" libraries (I maintain them separately and reuse in multiple projects, but I don't copy-paste them and instead maintain only one copy). In this case it's a fair point that if you're browsing the API reference it's extra work to have to open up the original sources and make the change there, but I disagree that it's making things any harder. I still have to properly run any relevant unit tests of the library I'm modifying, still have to make a proper commit, etc., and going from the API reference to the source code takes at most a few seconds (since the API reference will tell me which exact file it is, so I just have to tell my IDE's fuzzy file opener to open up that file to me.) and is still a tiny fraction of all of the things I'd need to do to make the change.
> I am not making things up when I say that the very first question I had about how to use this module, either is not answered, or I couldn't find the answer. That question was "what regular expression syntax is supported?". This is such a fundamental question, yet there is no answer provided.
Most decent text editors support something like go to definition. Your entire comment seems to be based on the idea that text editors only support basic search, which is simply false.
Personally I'm quite content with both experiences. But it really is just a matter of preference.
At least moderately advanced text editors often interoperate with symbols tables, so you can jump to a definition. But even with grep, you can usually do it in a way where you differentiate between definition and use. But I am not arguing that you should not use advanced tools if you like is, the deeper point is that you can always use advanced tools even with headers, but you can not go back in a language designed around advanced tools and work with simple tools. So it is strictly inferior IMHO to design a language around this additional cmplexity.
I think the person you're responding to must know all of this. This is stuff that's obvious to anyone who has ever written any code that required using libraries. Unfortunately , people like to pretend to have a gripe with something on the internet just for the sake of arguing. This is the only conclusion I can arrive at when people appear to seriously propose reading a header file in a text editor is somehow better than reading documentation in a purposefully designed documentation format. It's like saying browsers are just a waste of time when you can just use Gopher for everything.
Or it just might be that different people prefer different things. I'm a hardcore fan of header files too. Vim is my preferred way of dealing with text and I can do all kinds of magic with it with the speed of though and I prefer to use as plain as possible text files. In the rare occasion when the documentation needs more than ascii stuff it's best practice to write a nice tex and friends documentation plus a real tutorial anyway. And full literate programming style is hard to beat when you are dealing with complex things.
It's fine to have preferences or cognitive inertia towards working a certain way. It's silly to pretend that doing things this way conveys some kind of universalist advantage or to conjure up a bunch of imaginary/highly niche scenarios (I'm remote coding over 28.8k at the bottom of the ocean and have no access to a browser anywhere!) that necessitate working this way for argumentative purposes.
The OP uses C libraries, and this is used to much simpler interfaces and much smaller dependency sets than the GP. So no, I don't think they know all of this.
But also, they probably to know how to keep their dependencies sane, and possibly think the best way to document that giant 2k lines interface is in a book. What are both really good opinions, that will never be really "understood" by communities the GP takes his libraries from just because it's not viable for them to do it.
The issue is that the coding style depends on whoever wrote the external library, not on you, so this ends up working only sometimes. You can probably find some other combination that will help you find what you're looking for (I do this all the time when using Github's web interface) but ultimately this is just a bad experience.
As someone who likes C header files, I enjoy manually maintaining them. Designing the interface separately from the implementation feels good to me, and a well-structured .h file is nicer to read than any auto-generated docs I've encountered.
It’s important to not conflate Java’s interface keyword with the more general notion of “interface” as in “API”. In Java, you generally don’t and can’t have a source-level representation of the API without it being interspersed with its implementation.
(You could imagine such a representation, i.e. remove all method bodies and (package-)private elements, but the result wouldn’t be valid Java. IDEs arguably could and should provide such a source-level view, e.g. via code folding, but I don’t know any that do.)
Even as a java programmer, I think this is a bad take. Java doesn't force separation of implementation and interface, and java interfaces also have a lot of weird stuff going on with them.
Java also has too many tools for this. You both have class/interface, but also public/private. I honestly think C does it better than Java.
> java interfaces also have a lot of weird stuff going on with them.
really? I know java pretty well, and there aint nothing weird there.
C uses conventions to produce an "interface" (ala a header file with declarations). Java uses compilers to produce an interface, which i do really like. You can ship that interface without an implementation, and only at runtime load an implementation for example.
> You both have class/interface, but also public/private.
And these are all othorgonal concerns. A private interface is for the internal organization of code, as opposed to a public one (for external consumption). That's why you might have a private interface.
> C uses conventions to produce an "interface" (ala a header file with declarations). Java uses compilers to produce an interface, which i do really like. You can ship that interface without an implementation, and only at runtime load an implementation for example.
You can do this in C as well, since it has separate code declarations and definitions. You conventionally put the declarations in the header and definitions in the source file. A C program can link against declarations alone, and the implementations can be loaded later using dynamic linking.
> And these are all othorgonal concerns. A private interface is for the internal organization of code, as opposed to a public one (for external consumption). That's why you might have a private interface.
But these are profoundly overlapping concerns. Interfaces also hide the internal organization of the code, and on top of this, you also have project jigzaw's modules (that absolutely nobody uses), but which also caters toward separating private implementations from public interfaces.
The problem is that you have to write the interface not only separately grom the implementation, but together with the implementation as well, which leads to duplication of information;
So? Yes you need some duplicatin but it forces you to put information useful to api users where they won't have to wade through pages of information not of interest to them. eventually they may need to read the source but a lot of common questions are answered by the header and so the small cost to make them is worth it.
of course javadoc can answer the same questions but then you have to run it.
The include file mechanism is a hack that was acceptable at the time when machines were extremely underpowered, so only the simplest solutions had a chance to be implemented within a reasonable time frame.
By now, of course, precompiled headers exist, but their interplay with #define allows for fun inconsistencies.
And, of course, they leak implementation as much as the author wants, all the way to .h-only single-file libraries.
If you want an example of a sane approach to separate interface and implementation files from last century, take a look e.g. at Modula-2 with its .int and .mod files.
Precompiled headers are a terrible misfeature. I ban them in any code base I am responsible for.
They encourage the use of large header files that group unrelated concerns. In turn that makes small changes in header files produce massive, unnecessary rebuilds of zillions of object files.
The clean practice is to push down #includes into .c files, and to ruthlessly eliminate them if at all possible. That speeds up partial rebuilds enormously. And once you adopt that clean practice, pre-compiled headers yield no benefit anyway.
I’m with parent - what if you don’t have the tool? What if there’s a syntax error in some implementation or dependency such that the tool chokes early?
Human readable headers are accessible out of context if the implementation. They also help provide a clear abstraction - this is the contract. This is what I support as of this version. (And hopefully with appropriate annotations across versions)
> I’m with parent - what if you don’t have the tool?
The "what if you don't have the tool" situation never happens in case of Rust. If you have the compiler you have the tool, because it's always included with the compiler. This isn't some third party tool that you install manually; it's arguably part of the language.
> What if there’s a syntax error in some implementation or dependency such that the tool chokes early?
In C I can see how this can happen with its mess of a build systems; in Rust this doesn't happen (in my 10+ years of Rust I've never seen it), because people don't publish libraries with syntax errors (duh!).
In this specific case, your tool requires a web browser (though I'm assuming that there is a non-web browser form of what is being sold here). Maybe you are in a situation where you only have terminal access to the machine.
Maybe you are on your phone just browsing github looking for a library to use
I'm sure people can continue to imagine more examples. It is entirely possible that we have different experiences of projects and teams.
> I’m sure people can continue to imagine more examples
Hopefully they’ll imagine more compelling examples.
If the hypothetical person’s phone is capable of browsing GitHub, I don’t see why they can’t also browse docs.rs. It renders well on small screens. That’s not a hypothetical, I’ve actually read the docs for libraries on my phone.
> The "what if you don't have the tool" situation never happens in case of Rust.
So it’s built into GitLab and GitHub? BitBucket? How easy is it to use on windows (i.e. is it is easy as opening a .h in notepad and reading it)? How easy is it to use from a command line environment with vim or emacs bindings?
I could go on. “Never” is doing a lot of heavy lifting in your assertion. I shouldn’t have to install a toolchain (let alone rely on a web browser) to read API documentation.
If you can open a browser, open docs.rs. The GitHub repo usually contains a link to docs.rs because that’s how people prefer to read the documentation.
If you prefer working without the internet that’s fine too. Use cargo doc, which opens the rendered doc page in a local web browser.
If you prefer being in a text editor exclusively, no problem! Grep for `pub` and read the doc comments right above (these start with ///). No toolchain necessary.
Look, most normal people don’t have some intense phobia of web browsers, so they’d prefer docs.rs. For the people who prefer text editor, it’s still a great experience - git clone and look for the doc comments.
The point is, the existence of docs.rs only encourages Rust library developers to write more and better documentation, which everyone, including text editor exclusive people benefit from. That’s why your comment sounds so strange.
> So it’s built into GitLab and GitHub? BitBucket?
No. It's built into the toolchain which every Rust developer has installed.
> How easy is it to use on windows (i.e. is it is easy as opening a .h in notepad and reading it)?
A easy as on Linux or macOS from my experience.
> How easy is it to use from a command line environment with vim or emacs bindings?
Not sure I understand the question; use how exactly? You either have a binding which runs `cargo doc` and opens the docs for you, or you use an LSP server and a plugin for your editor in which case the docs are integrated into your editor.
> I shouldn’t have to install a toolchain (let alone rely on a web browser) to read API documentation.
If you want you can just read the source code, just as you do for any other language, because the docs are right there in the sources.
For publicly available libraries you can also type in `https://docs.rs/$name_of_library` in your web browser to open the docs. Any library available through crates.io (so 99.9% of what people use) have docs available there, so even if you don't have the toolchain installed/are on your phone you can still browse through the docs.
I know what you're going to say - what if you don't have the toolchain installed and the library is not public? Or, worse, you're using a 30 year old machine that doesn't have a web browser available?! Well, sure, tough luck, then you need to do it the old school way and browse the sources.
You can always find a corner case of "what if...?", but I find that totally unconvincing. Making the 99.9% case harder (when you have a web browser and a toolchain installed, etc.) to make the 0.1% case (when you don't) easier is a bad tradeoff.
I don't understand how you don't understand the order of magnitude difference in flexibility, utility, availability, etc between needing to run a specific executable vs merely opening a text file in any way.
"you always have the exe" is just not even remotely a valid argument.
> "you always have the exe" is just not even remotely a valid argument.
Why? Can you explain it to me?
I'm a Rust developer. I use my work station every day for 8 hours to write code. I also use `cargo doc` (the tool for which "I always have the exe") every day to look up API docs, and in total this saves me a ton of time every month (probably multiple hours at least, if I'm working with unfamiliar libraries), and I save even more time because I don't have to maintain separate header files (because Rust doesn't have them).
Can you explain the superior flexibility and utility of "merely opening a text file" over this approach, and how that would make me (and my colleagues at work) more productive and save me time?
I'm not being sarcastic here; genuinely, please convince me that I'm wrong. I've been a C developer for over 20 years and I did it the "opening a text file" way and never want to go back, but maybe you're seeing something here that I never saw myself, in which case please enlighten me.
I don’t understand how you don’t understand that that’s always an option. Rust source files are written in plaintext too.
There are a few people in this thread, including you, who claim that they vastly prefer the output of documentation to be plain text in a single file rather than linked HTML files OR reading the source in multiple plaintext files.
That’s a preference, so y’all can’t be wrong. But consider that if this preference was even slightly popular, cargo doc would probably get a —-text option that output everything in a single text file. The fact that it doesn’t have it tells me that this preference is very niche.
Yes, it works with GitHub, GitLab, Bitbucket, and everything else. It's built into the compiler toolchain.
It works with every syntax that you can compile, because it uses the compiler itself to extract the documentation.
Yes, it works on Windows too. Rust supports Windows as a first-class platform. It works with dependencies too (the docs even link across packages). The fragmentation of C tooling and unreliability/complexity of integrating with C builds is not a universal problem.
Rust's built-in documentation generator creates HTML, so anything with a browser can show it. It also has JSON format for 3rd party tooling.
The same language syntax for the documentation is understood by Rust's LSP server, so vim, emacs, and other editors with LSP plugins can show the documentation inline too.
I've been using this for years, and it works great. I don't miss maintaining C headers at all. I write function definitions once, document them in the same place where the code is, and get high fidelity always up-to-date API docs automatically.
The "what if you don't have the software" argument doesn't hold water for me. What if you don't have git? What if you don't have a text editor? What if you don't have a filesystem?
Most programming language communities are okay with expecting a certain amount of (modern) tooling, and C can't rely on legacy to remain relevant forever...
Sounds like COM/DCOM from ~1995. Every API had a public interface including a description. You could open the DCOM Inspector, browse all the APIs, and see the type signature of every function and its docs.
headers perform the same job for all code, not just code that's in some library.
Frankly your description of what you just called easy sounds terrible and pointlessly extra, indirection that doesn't pay for itself in the form of some overwhelming huge win somewhere else. It's easy only if the alternative was getting it by fax or something.
Having to make an entire separate file to mark something as public rather than just having a keyword in the language sounds to me "terrible and pointlessly extra". It's not like you can't just put all your public stuff in it's own file in Rust rather than putting private stuff in it as well; empirically though, people don't do this because it's just not worth the effort.
Header files are really a weak hack to deal with resource constrained platforms from the 70s. They only work if you stick to a convention and pale in comparison to languages like Ada with well architected specification for interfaces and implementation without ever needing to reparse over and over again.
I do enjoy using C but that is one area where it should have been better designed.
The way C handles header files is sort of "seems-to-work" by just blindly including the text inline.
I know this is not a much-used language, but in comparison, Ada did a pretty nice thing. They have the concept of packages and package bodies. The package is equivalent to the header file, and the package body is the implementation of the package.
I remember (long ago when I used ada) that everyone could compile against the package without having the package body implementation ready so the interfaces could all work before the implementation was ready.
an in another direction, I like how python does "header files" with "import". It maps easily to the filesystem without having to deal with files and the C include file semantics.
I think there may be a difference in thinking that underlies the difference in opinion here.
In my experience, having a header file nudges you to think about interface being a _different thing_ to implementation - something that (because you need to) you think about as more fundamentally separate from the implementation.
Folks who think this way bristle at the idea that interface be generated using tooling. The interface is not an artifact of the implementation - it’s a separate, deliberate, and for some even more important thing. It makes no sense to them that it be generated from the implementation source - that’s an obvious inversion of priority.
Of course, the reverse is also true - for folks used to auto-generated docs, they bristle at the idea that the interface is not generated from the one true source of truth - the implementation source. To them it’s just a reflection of the implementation and it makes no sense to do ‘duplicate’ work to maintain it.
Working in languages with or without separate interface files nudges people into either camp over time, and they forget what it’s like to think in the other way.
This thread feels weird to me because when I write code I do think about my public API, have even sketched it out separately looking at the desired usage pattern, but never felt the need to save that sketch as anything other than as part of the documentation. Which lives next to the code that implements that API.
I think it is telling that the handful of languages that still have something akin to .h files use them purely to define cross-language APIs.
Available in most compiled module languages, either separately, Modula-2, Modula-3, Ada, Standard ML, Caml Light, OCaml, F#, D.
Or it can be generated either as text, or graphical tooling, Object Pascal, D, Haskell, Java, C#, F#, Swift, Go, Rust.
All with stronger typing, faster compilation (Rust and Swift toolchain still need some work), proper namespacing.
Unfortunately C tooling has always been more primitive than what was happening outside Bell Labs, and had AT&T been allowed to take commercial advantage, history would be much different, instead we got free lemons, instead of nice juicy oranges.
At least they did come up with TypeScript for C, and it nowadays supports proper modules, alongside bounds checked collection types.
I find it pretty frustrating to have the documentation in a different file from the source code.
When maintaining the code that means I have to go to a separate file to read what a function is supposed to do, or update the documentation.
And when reading the documentation, if the documentation is unclear, I have to go to a separate file to see what the function actually does.
Granted, the implementation can get in the way if you are just reading the documentation, but if you aren't concerned about the implementation, then as others have said, you can use generated documentation.
I used to think like this, but then I discovered generating (prj_root)/types.d.ts. It doesn’t do anything technical because types are in src/**/*, but I do that to generate a quick overview for a project I’m returning to after a month.
Maintaining header files is tedious and I often resorted to a kind of “OBHF.h” for common types, if you know what I mean. Otherwise it’s too much cross-tangling and forwards. Even in ts I do type-only src/types.ts for types likely common to everything, mostly because I don’t want pages of picky this-from-there this-from-there imports in every module.
As for public/private and sharing “friends” across implementation modules, we didn’t invent anything good anyway. I just name my public private symbols impl_foo and that tells me and everyone what it is.
That said, I wouldn’t want to make html out of it like these *-doc tools do. Using another program to navigate what is basically code feels like their editor sucks. My position on in-code documentation is that it should be navigatable the same way you write it. External tools and build steps kill “immersion”.
I don't really program in C much so please correct me if I am wrong. There is a flaw in header files in that they work the exact same for dynamic vs static linking, right? If I am making a library in C for static linking, I need to put my internal details in the header file if I want the user's compiler to be able to use those details. But putting them in the header files also means they are part of the public interface now and should no longer be changed.
Basically, I cannot do something like a struct with an opaque internal structure but a compile time known layout so that the compiler can optimise it properly but the user cannot mess with the internals (in language supported direct ways).
That is less about header files, and more about how machine code works.
If you want to have some abstract type where you don't let people know anything about the innards, but you do have an explicit interface which enumerates what you can do with it, then yes - you can only really pass around pointers to these things and people outside your abstraction can only pass references not values.
If you want people to be able to pass your abstract type by value (among other things), then either you need to let them know how big the thing is (an implementation detail) or you have to expose the copy implementation in such a way that it could be inlined (more implementation details).
Sometimes, the "pure abstraction" approach is best where you only ever deal with pointers to things, and other times the "let's pretend that people do the right thing" approach is best. I don't see this as a header file thing though.
I disagree with you on this. In another language with explicit public / private separation, the compiler can have access to the internal layout of a type (and thus optimise on it) without letting the developer mess around with it directly. I am assuming static compilation of course. Across a dynamic boundary, I would expect this compiler to behave like a normal C compiler and not use that layout.
In a header file, the information for the compiler and the user are the exact same which means you can't reduce your public interface without straight up hiding more of yourself.
Personally, I'm happy to just let a Sufficiently Advanced Compiler do link time optimizations to deal with that level of optimization and either take the hit, or make more things public while that compiler doesn't exist.
Let the header files be written for people to read first, and only if there is actually a big performance issue, and the problem is the interface do you need to revisit it (and I'm not just saying this - I will frequently and happily go back and modify interfaces to allow for less data movement etc, but most of the time it really isn't important).
I think you are probably right to disagree with me though - I think I should have said that it is more of a limitation on how object files work, rather than how machines work. Object files aren't the only way things can work.
With C++ you have the third option where the compiler makes sure that the "people will do the right thing" with the private keyword - assuming they're not doing some weird pointer math to access the private members..
Of course, you'll have to deal with ABI stability now but it's all tradeoffs for what your requirements are.
Of course you should do the right thing, but if you want to break the private of C++ it is much easier to "#define private public" before including the header file.
Right, but as soon as you have private stuff in your header file, that is leaking implementation details. Yes it is kind of true that these are compile time checked to make sure that people don't do the wrong thing, but it is still an implementation detail that is leaking.
It comes down to a cost benefit thing - is the cost of poorer readability worth it for mitigating the risk of people doing the wrong thing? My experience says no, other people's experience says yes. Probably we are working on different problems and with different teams.
In C programs only the external definition of an interface goes into the header of a library but not implementation details (there could be headers intentionally exposing details for internal use, of course).
The problem is real for C++.
Optimizers can look across translation units nowadays (link-time optimization), so there is no reason to expose internal details in a header for this. For dynamic libraries this does not work of course, but it also shouldn't.
Even for C it's normal ways true: When size of structures have to be known to the user (if they are supposed to keep them on stack or mallox themselves or whatever) the "private" structure often ends up in a "private" header. (There are ways to still hide it, but they cause work to keep things proper)
And then there are cases where (often die to performance) you want inlining of some operations without relying on Link time optimisation, then implementation has to go to headers, too.
The proper way is not exporting implementation details at all, instead define opaque types in your header files like this: `typedef struct ssl_st SSL;`. This comes from OpenSSL, it means users can use `SSL *` pointers, but they don't know what those pointers point to.
Of course you can also have internal header-files within your own project, which you don't share with the end-users of your product.
Object Pascal (not the original Pascal) versions like Delphi and Free Pascal have syntax and semantics for interface and implementation sections of the module. Wouldn't be surprised if Modula-2 and Ada had that too.
That was inherited from UCSD Pascal, and also incorporated into ISO Extended Pascal, which was supposed to be a more industry friendly revision of ISO Pascal, but by then Object Pascal was the de facto standard.
Modula-2 modules are based on Xerox Mesa, and do have split sections, as does Ada.
Additionally, Modula-2 and Ada modules/packages have a powerful feature that is seldom used, multiple interfaces for the same module implementation, this allows to customise the consume of a module depending on the customer code.
I remember int/impl sections since the 1990’s turbo pascal, which wasn’t “object” still, iirc. Also, commercial closed-source units (modules) were often distributed in a .tpu/.dcu + .int form, where .int was basically its source code without the implementation section.
It was literally the unit with implementation part just missing. Sort of a header that you can just read(?). Idk if it played a role in compilation, probably not. But some commercial libraries packaged them as well. Here, look at this random repo: https://github.com/keskival/turbo-pascal-experiments/tree/ma... -- few int files at the end of a list.
>Header files are one of the things I miss the most about languages that aren't C. Having a very clear distinction between public and private, and interface and implementation is one of my favourite things about C code (at least the way I write it).
You don't need header files for that.
A dead simple to write tool that parses the actual non-duplicating info, files, and prints you the interfaces and access qualifiers for each method as such, presenting you everything or just the public ones etc, should suffice.
I love headers but I wish you could split them in two so that private functions and variables can line in the c file. This would help reduce a lot of header bloat as well.
It is perfectly valid to use more than one header files: some of them can be public (meant to be seen by users of your library), others can be private or internal (only used by your own sources).
Also, usually it's pretty rare to have things internal to one C file that need explicit prototypes. It's easier to just put things in the right order so the funtion definition etc is before its use.
If you want something similar in Python, you could structure your code following the port and adapter pattern. Very effective, especially if paired with hexagonal architecture and type checking libraries like pydantic.
I used to like having nice strings to go with my asserts and would often do something like: assert(condition && "explanation of assert"). Now I think that the whole point of an assert is that it is for errors that require a code change (i.e. it is not recoverable at runtime) and so the very first thing that will happen in the investigation will be looking at the code with the assert - so a comment will do just fine if things aren't obvious. We also know that the programmer is going to need to understand the code that they are changing, and we also know that whoever wrote the code must have had a wrong assumption, so while their documentation is a good clue, it is also to be treated with some level of suspicion. As a result, I go for a simple assert, occasionally with a comment that explains why the assert should never fail.
Being able to use the value asserted is nice sugar though. I will take that :)
> and so the very first thing that will happen in the investigation will be looking at the code with the assert
And the very next thing that will happen is me swearing up and down that I wished I captured more context at the point of error so I could understand the corner case much more easily.
assert, on failure, should dump as much function and stack context as it possibly can generate; and possibly, a string tag for _user_ convenience in reporting on and differentiating for themselves the specific error condition.
mainframes are great for this. You get a custom tagged error message that conveys the subsystem, error level, and error number along with a text error string. This makes cataloging and classifying errors automatic for everyone who sees and conveys them. Then you can use the system record store to go get the context information generated at the site of the error itself and build a much clearer idea of why the particular operation failed.
I'm more with skeeto on this that asserts should make it easy to slip into a debugger to get that further context you're looking for: https://nullprogram.com/blog/2022/06/26/
One thing that severely hinders assertion strings is that people can't seem to decide whether the string should state the intended case or the failure case.
By contrast, being able to dump out the inputs to the failing expression is always useful. This is mostly discussed in the context of test-suite asserts, but should probably be generalized.
One reason I like assert strings is that they can assist bug triage in a large organization, assuming that (as in my environment) the string is available outside of the core/kernel dump, probably in the kernel ring buffer/logs. Often, keying off of line number won't work across different branches, maintenance releases, patches and the like, so having a unique-ish string to search known issues/chat history/etc with is valuable. Sure, you can check out the code corresponding to the build that produced the core and find the code context from there, but that's another step that takes time, especially if you have to traverse a bunch of mappings like "oh, the core's from a machine on B_FOO_1337, which according to this one service corresponds to commit abcdef123, which is in repo baz, which I don't actually have checked out and/or fetched recently, oops." In an environment of this sort, it's also frequently not super quick or straightforward to get a debugger set up with the appropriate symbols to open the core and see the context - if a person running through a big list of defects can simply plug the string into a search and go oh, yup, it's known issue #12345 vs. having to hunt for the correct symbols, copy the (potentially several gb!) core around, get gdb to happily open it, etc, that eventually adds up to big time savings. Finally, compiler optimizations can make it so that, while a value is present in the core, the debugger can't automatically find it and gives you that <value optimized out> thing. While you usually can go digging around in the registers to extract it, that's a pain and yet more time. If you put the value(s) of the failed assertion in the string, that's one less spot for someone doing triage to get hung up on when trying to tell whether an issue is something new or just frequency of a known issue.
For stuff I'm just writing by and for myself, yeah, I take your approach. For software that will generate failures I may not be the first person in an organization to have to look at, I add friendly strings.
Volume controls also shouldn't just be a flat wideband gain - they should respect how we actually perceive sound so the timbre doesn't change as the level changes (when you turn the volume down, you are typically left with just the stuff in the vocal frequency range, and lose all the bass etc).
Doing this stuff well is pretty hard (e.g. designing filters that can do that kind of volume adjustment is hard because you want to be constantly adjusting them, which means you need to be super careful with your filter state) but I have heard what it sounds like, and once you hear it you get angry at all other volume controls.
> Volume controls also shouldn't just be a flat wideband gain - they should respect how we actually perceive sound so the timbre doesn't change as the level changes (when you turn the volume down, you are typically left with just the stuff in the vocal frequency range, and lose all the bass etc).
The amp I'm upgrading from was interesting in this regard. In addition to the main volume knob, it had a loudness knob. The manual actually recommended keeping the volume knob fixed most of the time and using the loudness knob to set the listening level throughout the day.
From the manual:
> 1. Set the LOUDNESS control to the FLAT position.
> 2. Rotate the VOLUME control on the front panel (or press VOLUME +/– on the remote control) to set the sound output level to the loudest listening level that you would listen to.
> 3. Rotate the LOUDNESS control counterclockwise until the desired volume is obtained.
Amazing. This is probably the correct way do make amp controls. I'd say the volume should be a multi turn trim potentiometer in the back of the device so you don't have to brief your guests on correct operation.
My cheap Behringer NU1000DSP that I use as a subwoofer amp can do that to some extent with its dynamic EQ, and you can set it up via USB with a PC app which is a huge step up from some crummy little LCD and buttons.
I gave myself the rule of no internet while on the train, so sometimes I would just accumulate a list of questions I wanted to answer later.
There is definitely something to it, and you can get heaps done, but it needs to be supported with some non-train time (e.g. for me, it was all the app store stuff, debugging with real hardware, etc)
reply