2

I have background with high level languages, such as Java/Kotlin/Scala. Now I need to create a C library, but it's pretty hard for me to work without closures.

There is the nice extension in GCC, called "nested functions", that (if I correctly understood) is exactly what I need. Also the Apple "blocks" extension looks interesting. But I need a code, that will work with any compiler.

Does any existing solution present? I've seen some projects, that allow generating C code: https://github.com/dbohdan/compilers-targeting-c. But I really don't need another language, just one feature - closures. (I think that it hard to maintain completely different language and result C code will be not optimized).

UPD Some explanation, why do I need closures.

Let say we have some function, connect(void (*on_failed_callback)());. It manages a connection in some way, and when the connection is lost, it calls the callback. And it takes 0 arguments, but I want to create some function that will bring some data with callback. If I've correctly understood, the most common used solution is to pass the callback with some arg connect(void (*on_failed_callback)(void *callback_arg), void* arg);. But it leads to a boilerplate code, that can be fixed by nested functions.

So next code:

void connect(void (*on_failed_callback)(void* arg), void* arg) {
    ...
    on_failed_callback(arg);
}

void print_error(char* error) {
    printf(error);
}

void main() {
    char *msg = "Failed connection";
    ...
    connect(print_error, msg);
}

Can be simplified to next:

void connect(void (*on_failed_callback)()) {
    ...
    on_failed_callback();
}

void print_error(char* error) {
    printf(error);
}

void main() {
    char* msg = "Failed connection";
    ...
    void callback() {
       print_error(msg);
    }
    connect(callback);
}

I mean, I want some tool/app, that analyzes my code with closures/nested functions and generates pure ANSI C code.

Uraty
  • 257
  • 2
  • 7
  • 5
    No. And I don't see what can be done with nested functions which cannot be done without. C is imperative language and is quite different from functional ones. Yet I find the knowledge of functional programming to be a significant help in understanding concepts such as recursion and functional purity, which can enhance the C skill and not weaken it. – Eugene Sh. May 29 '19 at 13:21
  • If you want to have closures, then you will need some kind of non-linear stack structure. But you can probably achieve something similar by providing some storage to hold the state that you need. You'd simply have to manage that storage yourself. – Tom Karzes May 29 '19 at 13:21
  • Since it is an extension, you cannot do this so that it compiles on all compilers. – klutt May 29 '19 at 13:25
  • 1
    ANSI C (which was renamed to ISO C in 1990) does not support nested functions or closures. So if you use the gcc extension, your code will not work with other compilers. – Peter May 29 '19 at 13:26
  • 1
    Possible duplicate of [Is there a a way to achieve closures in C](https://stackoverflow.com/questions/4393716/is-there-a-a-way-to-achieve-closures-in-c) – Tom Karzes May 29 '19 at 13:28
  • You could probably clarify your intention by showing some example code using nested functions – Eugene Sh. May 29 '19 at 13:31
  • 2
    Anyway, C is a rather low level language. From the very beginning of K&R C in the 70', it was intended to be compiler friendly, not to save some typing for the programmers. You will have to turn to C++ if you want some more goodies... – Serge Ballesta May 29 '19 at 13:35
  • 4
    The GCC nested functions are not proper closures, they can only refer to their environment while it is alive! – Antti Haapala -- Слава Україні May 29 '19 at 13:37
  • I afraid your proposed code won't work for the reason @AnttiHaapala stated above... – Eugene Sh. May 29 '19 at 13:39
  • The block approach could work, you'd just need to find a way to desugar it :D – Antti Haapala -- Слава Україні May 29 '19 at 13:40
  • 1
    This could be of interest: https://techtalk.intersec.com/2014/11/blocks-rewriting-with-clang/ – Antti Haapala -- Слава Україні May 29 '19 at 13:42
  • The only difference between your example 1) and 2) is that 1) is readable and reasonably type safe, while 2) is unreadable and dangerous. Why would you want the latter? This kind of meta programming reasoning only leads to rotten code. – Lundin May 29 '19 at 13:48
  • Example 1) is readable when we have just one argument in callback and calling function that has only callback as argument. When args number increases, readability dramatically decreases. – Uraty May 29 '19 at 13:53
  • No it doesn't, you are simply not used to it. This is all wildly subjective options. Besides, the reasoning that the callback belongs to main() is flawed from an OOD point of view. The callback should be allocated together with all other "connection-related" behavior. It shouldn't be in the caller. – Lundin May 29 '19 at 13:55
  • If what you're worried about is readability of the function pointer, it's highly recommended to use a typedef for the function pointer. – dbush May 29 '19 at 13:59
  • Using C results in boilerplate code, you cannot do much about it. You have proposed another language based on C which has closures, and you want a compiler for it. Good luck. There is a mainstream C based language with closures. It is called C++. People generally use C++ rather than half-baked homebrew solutions. Perhaps you want to give it a try. If you want a C++ compiler that produces C code as an intermediate representation, there is such a thing too. – n. m. could be an AI May 29 '19 at 14:58

2 Answers2

4

For the specific example you have asked about there is an approach with C that achieves something similar to a closure in that it packages a function pointer with the data needed to be used with the function.

typedef struct {
    char sBuff[128];              // data for the function to use
    void (* func) (char* error);  // pointer to function to execute
} ErrorMsg;

void connect(ErrorMsg myMsg) {
    ...
    myMsg.func(myMsg.sBuff);  // call the function with the packaged data
}

// the function that we will be encapsulating with the data to be
// used.
void print_error(char* error) {
    printf(error);
}

void main() {
    ErrorMsg msg = {"Failed connection", print_error} ;
    …
    connect(msg);  // invoke our function package and its data.
}

And if at the time that the function package is used, you would like to add additional data, you could do so along the lines of the following.

typedef struct {
    char sBuff[128];              // packaged data for the function to use
    void (* func) (char* error, int iExtra);  // pointer to function to execute
} ErrorMsg;

void connect(ErrorMsg myMsg) {
    ...
    myMsg.func(myMsg.sBuff, errno);  // call the function with the packaged data and extra info
}

// the function that we will be encapsulating with the data to be
// used.
void print_error(char* error, int iExtra) {
    printf(error, iExtra);
}

void main() {
    ErrorMsg msg = {"Failed connection: errno %d", print_error} ;
    …
    connect(msg);  // invoke our function package and its data.
}
Richard Chambers
  • 16,643
  • 4
  • 81
  • 106
  • Yes of course, good answer. This is not only common C praxis, it is much better program design than some localized "closure" meta programming that only serves to create tight coupling between an object and some unrelated caller. – Lundin May 29 '19 at 14:00
1

Does any existing solution present?

It depends on what you mean. Standard C does not provide closures, nested functions, or lambdas / blocks as language features. But there is nothing you can do in some other language that has any or all of these that you cannot do in C without. In fact, some languages that do have these features are themselves implemented in C.

In particular, your first example is approximately the normal C idiom for generic callbacks. The callback function accepts a pointer to an object containing any data it needs, and the callback-registration interface accepts a pointer to the appropriate object together with a pointer to the function. When the callback is triggered, the registered data pointer is passed to it.

Note, by the way, that this is a more general facility than closures alone afford, because the data on which the callback will operate do not need to be in scope where the function is defined. Among other things, this permits standard, reusable callback implementations to be provided, where the association with specific data is made only at the point of callback registration. In the event that multiple callbacks can be simultaneously associated with the same event, it also allows the same callback to be reused, with different data, for the same event.

John Bollinger
  • 160,171
  • 8
  • 81
  • 157