Skip to content

Problem with enums and translate-c #5242

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Closed
donaldcallen opened this issue May 1, 2020 · 7 comments
Closed

Problem with enums and translate-c #5242

donaldcallen opened this issue May 1, 2020 · 7 comments

Comments

@donaldcallen
Copy link

donaldcallen commented May 1, 2020

Working on an application that uses gtk3, I am using translate-c to turn gtk.h into gtk.zig (I'm essentially doing the @cimport in a separate makefile step, to avoid doing the transaction on every recompilation; gtk.h plus all its includes is huge).

I want to call

GtkWidget *
gtk_dialog_new_with_buttons (const gchar *title,
                             GtkWindow *parent,
                             GtkDialogFlags flags,
                             const gchar *first_button_text,
                             ...);

which gets translated as

pub extern fn gtk_dialog_new_with_buttons(title: [*c]const gchar,
parent: [*c]GtkWindow, 
flags: GtkDialogFlags, 
first_button_text: [*c]const gchar, ...) [*c]GtkWidget;

The difficulty is the flags argument. GtkDialogFlags is an enum in C. It gets translated as

pub const GTK_DIALOG_MODAL = @enumToInt(enum_unnamed_315.GTK_DIALOG_MODAL);
pub const GTK_DIALOG_DESTROY_WITH_PARENT = @enumToInt(enum_unnamed_315.GTK_DIALOG_DESTROY_WITH_PARENT);
pub const GTK_DIALOG_USE_HEADER_BAR = @enumToInt(enum_unnamed_315.GTK_DIALOG_USE_HEADER_BAR);
const enum_unnamed_315 = extern enum(c_int) {
    GTK_DIALOG_MODAL = 1,
    GTK_DIALOG_DESTROY_WITH_PARENT = 2,
    GTK_DIALOG_USE_HEADER_BAR = 4,
    _,
};
pub const GtkDialogFlags = enum_unnamed_315;

The problem is that in C, enums are their integer values, whereas in zig a distinction is made between the enum type and its ordinal values. So if I try to call gtk_dialog_new_with_buttons with a flags argument of, say, GTK_DIALOG_MODAL, as translated by translate-c, I get an error from the compiler, complaining that it is expecting the enum type and I handed it a c_int:

Semantic Analysis [625/987] ./book.zig:50:62: error: expected type 'zlib.gtk.enum_unnamed_315', found 'c_int'
                                                            g.GTK_DIALOG_MODAL,
                                                             ^
/usr/home/dca/Software/newcash_zig/zlib/gtk.zig:21181:26: note: zlib.gtk.enum_unnamed_315 declared here
const enum_unnamed_315 = extern enum(c_int) {

But the C function I'm calling wants the ordinal value that I am supplying. The problem is that translate-c has defined the flag parameter of that function as a Zig enum, and
what the C function wants is an ordinal value.

I have a C interface file in this application that I am using to get around situations like this where translate-c does not work perfectly or at all, e.g., when C pre-preprocessor macros are needed. I can work around this problem by creating an interface function that expects an integer for the flags argument.

@Vexu
Copy link
Member

Vexu commented May 1, 2020

This is intentional. If you want the integer value you can use cimport.GTK_DIALOG_MODAL and if you want the enum value you can use an enum literal .GTK_DIALOG_MODAL. And if you need to convert between integer and enum value you can use the @enumToInt and @intToEnum builtins.

@donaldcallen
Copy link
Author

donaldcallen commented May 1, 2020

I'm sorry, but you are missing the point. The zig function prototype that translate-c creates for the discussed function specifies the flag argument type as a (zig) enum. But the C function that it represents wants an ordinal value for the flag. But if you try to pass an ordinal value, the compiler complains, because an integer is not the same as an enum in Zig as it is in C. The issue is that the Zig function prototype produced by translate-c is incorrect, because it is not accounting for the different semantics of Zig vs C enums.

@SpexGuy
Copy link
Contributor

SpexGuy commented May 1, 2020

This has been discussed previously in #2115. The ultimate resolution was explained here. I disagree with Andrew's assessment of C's type system, and you probably will also, but this is the reasoning for the current behavior.

In terms of correctness, the generated enum is extensible, so all flag combinations are valid values for the enum. They just don't have names. So the generated function signature is correct, it's just a bit annoying to work with because you have to use @intToEnum at the call site.

Zig doesn't currently have a nice way to represent flag types that is guaranteed to be ABI-compatible with a C enum, except using an untyped integer. #5049 is a proposal targeted at fixing that problem. Once an ABI-compatible flag type is in the language, we could consider trying to identify enums that are used as flags in translate-c and translate them as flags.

@donaldcallen
Copy link
Author

donaldcallen commented May 1, 2020

" So the generated function signature is correct, it's just a bit annoying to work with because you have to use @intToEnum at the call site."

That suggests to me that if I convert an integer flag, e.g., GTK_DIALOG_MODAL. back to an enum, as you say, that what will actually get passed to the C function is its ordinal value? And so despite the to-ing and fro-ing, the right thing happens? And if you look at the translated definition of GtkDialogFlags in my original post, the @intToEnum call might not even be necessary, since GtkDialogFlags.GTK_DIALOG_MODAL is available, which carries the enum type. If my speculation in this paragraph is correct, then perhaps this is not the problem I thought it was. I will leave it open until I've read issue #2115, which you cited.

@SpexGuy
Copy link
Contributor

SpexGuy commented May 1, 2020

Yeah, extern enums are guaranteed to be ABI-compatible with C enums. The @intToEnum and @enumToInt builtins translate to no-ops with extern enums, they are just to appease the type system. Under the hood extern enums have the same byte representation as their ordinal value and they will always be passed in the same way (in a register vs on the stack). They also are guaranteed to have the same byte swapping behavior.
@intToEnum(c.GtkDialogFlags, c.GTK_DIALOG_MODAL | c.GTK_DIALOG_DESTROY_WITH_PARENT) will generate a GtkDialogFlags with value 3.

@donaldcallen
Copy link
Author

Ok, good. Thanks much for the explanation. I am not quite at the point of testing the code in question, but certainly proceeding using the path you set me on is getting me past the compilation problems. I just have to sort out some other stuff before I test. I expect this will all just work. If nothing else crops up relevant to this, I will close the issue.

@donaldcallen
Copy link
Author

I did test the code written as discussed in previous messages and it works fine on a Slackware Linux system. I also happen to have a FreeBSD system at the moment and tested it there and it seg-faulted. Some digging with gdb suggests to me that the problem has nothing to do with the issue at hand, so I'm going to close this one.

I also note that there is discussion of this issue in the Zig documentation, however terse, that extern enums are C ABI compatible and the C translator did translate the gtk enum we're discussing as extern. So part of my concern here was due to my own failure to read the documentation as carefully as I should have.

@ziglang ziglang deleted a comment Feb 5, 2025
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

No branches or pull requests

3 participants