When creating a command-line app in C, what it's the idiomatic way to structure your source code?
Having played around with Go, I've been doing it like this:
Declaring a struct at the top of a header file, along with several functions like new, set, delete, along with any functions that take this struct as an argument. Also any relevant defines, like Maxes an mins for elements in the struct.
The header and source file are named after the struct, and so are all the functions. (user.h, struct User, new_user(), etc)
Go returns errors along side every function return, so the first struct element is int err, and the functions, which all return a struct, always return an error/status as well.
So after compiling I have a user.o file that contains all the user-related stuff.
A make file prevents unnecessary recompiling, and a test file contains my main, which calls and tests all the functions with assert().
No, sorry, I meant that this is how I've been organizing my c code based on my experience with Go...
Joshua Adams
Seems okay, no need to exaggerate the number of header files, but no need to cram everything into one. Not sure why you'd return a struct for errors or have err as an member in the struct though.
Jayden Foster
You want to look at the „fork()“ command and depending on what you want to achieve, „pipe()“, sockets or posix-threads might come in handy..
Nolan Butler
Cool. Yeah, that's how go does it, so I'm used to having an err value returned along with what I actually want.
The only alternative I see is to pass in an extra variable to be populated with the status, or always have the function return a status and pass in an extra variable for the return value, or maybe use the global error value, but I don't know anything about that..
Nicholas Reed
Just return the status as return value, and pass values to the function as references.
Isaiah Jones
I drank the functional programming Kool-ade, so this gives me side effect anxiety
John Carter
If you're looking to write code that focuses on correctness, don't write C or Go. If you're looking to write efficient code, then you have to deal with passing memory addresses. Anyway, you literally considered using a global state for error in the post above, so I'm not sure how sincere you are. Passing structs by copy and making new allocations and copies all the time sound extremely inefficient and cumbersome.
Gabriel Hall
I suppose I just want to cut *with* the grain of the C language as much as possible, without making terrible blunders that newer languages prevent.
Austin Thomas
Passing by reference is not a "terrible blunder". I'm not even sure why you find it so objectionable, you're passing in the state that needs to be modified rather than relying on implicit state anyway.
Angel Thomas
>modifying state
Landon White
You're either autistic or trolling at this point, I tried answer seriously but this is just inane shitposts.
Carson Fisher
If you can, split it into library and slim wrapper that just parses flags, decides on inputs/outputs (e.g. what files), and just runs the library. >error handling whatever you do, don't fall into errno pattern. it's a fucking bullshit that should not be replicated. there are several ways you can do it: >return integer this is usable when you would return void otherwise or can return with out pointer negative means error, zero means ok, positive if ok has some meaning check for error by comparing the if result < 0 { handle error } it's a simple way popular pattern is a context struct. you can put some error member into it. this fits into structs that serve as a state of a process, not as a plain data representation. bad patterns: reserving range from an actual return (e.g. ssize_t), out pointer for error you can also just panic >new_user() rather user_new(), this closer to namespacing
Lookup persistent data structures. That's a way to make copying not that inefficient.
Henry Barnes
This is good info, thanks! I'm glad you're encouraging me to avoid errno, I wasn't looking forward to using it.
I get the part about using a struct with an error member for state, not as an object, but the part that follows, the bad pattern, I don't understand. Just don't use a pointer for an error return?
Alexander Garcia
Good quick read, of the choices I think I like this way best:
val = strtonum(buf, min, max, &errstr); if (errstr) abort();
Daniel Murphy
>aborting because of invalid input Sure is fizzbuzz-tier in here.
Owen Gonzalez
google "cmake hello world"
Jace Sanders
nah I was pointing on this pattern: but idk, maybe I'm biased. imho error should be the return value and result should be returned through the pointer passed to the function
Andrew Bell
As for the project layout, you can also consider using CMake. CMake is one of those very convoluted ways that has million ways to do badly and very few, not well described, to do well. Similar to C++. It also has the worst official tutorial I've ever seen. Few resources on doing it well: pabloariasal.github.io/2018/02/19/its-time-to-do-cmake-right/ youtube.com/watch?v=bsXLMQ6WgIk
Isaiah Young
Do library functions mostly work this way, clobbering the arguments and returning the result status?
Parker Morales
It's very common. But it's C. if there isn't some official way to do things, people will do it in every way possible. Maybe I'm retarded and wrong.
Robert Collins
Try to decouple your structs and functions as much as possible. It's fine to have constructors and destructors like struct_new() and struct_del() but if you end up with a bunch of functions all called struct_dosomething() that all returns void and all take as first and often only argument a struct pointer you're in for trouble. This is sort of proto-OOP and should be avoided. Look at your functions and see which struct members they use and then pass them directly instead. Use pointers if you want to modify multiple members. Your functions will become much more general and if they start taking too many arguments you'll know you have to split them. Farther into a project I often found myself wanting to use struct related functions without the struct. This also avoids hidden states.
Nicholas Rogers
Hmm, good point. I've just realized I've essentially copied the same function for use with different structs.
But where do you put these general functions that still are kinda related to the struct/object?
Evan Cooper
maybe check Plan 9 (alternatively 9front) and OpenBSD codebases. it's a nice C and they use a lot of cmdline tools
Charles Robinson
You can still put them in a file called structname.c and call them structname_dostuff() but reading them will be much clearer if you can at a glance see what they return and take as input. Also some other things: Put includes primarily in the .c file and not in the .h file. This avoids circular includes. Const everything. This will look strange to begin with but actually makes things more readable if you can tell which values are modified farther down and you will avoid bugs. This is something rust gets right. If you want to split something into separate functions but can't figure out how you can use unnamed scopes as an option: function() { { first thing this fuctions does } { second thing this function does } } Again this is primarily for readability. Note that variables declared inside unnamed scopes can't be used outside. You'll know variables declared outside are important. Kind of like lambdas.
Isaiah Hughes
Looks like today is "Refactor Day" for me. Best get to it. Thanks for all the help!