Hi all,
There are already lots of discussions on this. I find that we need to discuss it further in a generalized manner: interfacing with C without a const equivalent qualifier.
const int value[] = {1,2,3};
This has been addressed in RFC #12216. With PR #12799 & 12824, below code results in the same C code:
let value = [1,2,3]
This is impossible now. For example, if we use Nim to create a callback function for an API in C, C compiler will complain on this.
For example, source of nimCopyMem is not marked as const, but when calling it, this parameter is casted to NIM_CONST void *.
static N_INLINE(void, nimCopyMem)(void* dest, void* source, NI size)
Since we don't have something equivalent to const which indicates the parameter is not modified in the function, I think it is hard to decided when to attach const to a parameter when generating C code.
Also seen in 12824, parameter of the generated $$ function do not have a const qualifier, if we pass in an value that has the const qualifier, some compiler may just warn on this, while others treat it as an error for some types.
How to deal with above cases?
Oh, by the way, In C I like to use const whenever possible.
We can simply make all parameters const since that's Nim semantics anyway. But I doubt it will work better.
Oh, by the way, in C, I like to use const whenever possible.
Good for you, but there is no evidence whatsoever that the cost of writing const everywhere (and the cognitive load that it implies!) prevents enough bugs to be a worth-while trade-off.
We can simply make all parameters const since that's Nim semantics anyway.
I can't understand this. The first parameter of nimCopyMem can't be const obviously.
The first parameter of nimCopyMem can't be const obviously.
Sure it can, the pointer is const, what it points to is not. It's const T* const vs const T*. And it's exactly this sophistry that implies that in practice "const" is useless: It's never applied consistently and if it were it would be overly verbose and it can be casted away so the C optimizer is better off not relying on it and computing the property on the fly...
It's also against C's spirit, you don't use types to enforce correctness, you use them to describe memory layouts, layouts don't have "readonly" properties. Correctness in C-land is approximated by testing.
Take nimCopyMem as an example, do you mean to change its prototype to this (below) ?
static N_INLINE(void, nimCopyMem)(void* const dest, void* const source, NI size)
I do care about coust T *, but not T * const. To me, below is far more better than the above one:
static N_INLINE(void, nimCopyMem)(void* dest, const void* source, NI size)
I meant to change it to:
static N_INLINE(void, nimCopyMem)(void* const dest, void* const source, NI const size)
But yeah, what's the problem again? "Generate a C function with const params" ... that's only important if you interface to C via compiling to C for the lack of better words. In other words, do not use .importc, header, use .importc, dynlib, interface via ABI, not via API.
There are truly readonly data in the physical world, such as ROM. If a value of type T is stored in ROM, what's the type of &T? It's const T *. How to model this in Nim?
If we need to define a callback function (type of a param is const T *) in Nim, using ABI would not help.
P.S. It's weird that my PR using raw const fails to pass t8967 on MacOS/clang, while your PR using NIM_CONST succeeds. This is magic. Is it compiled as C++?
P.P.S. I just found that Nim uses -w option when invoking gcc. In my projects for embedded systems, gcc is invoked in makefile, and I am enjoying those warnings, :)
There are truly readonly data in the physical world, such as ROM. If a value of type T is stored in ROM, what's the type of &T? It's const T *. How to model this in Nim?
Just use T. If the data cannot be modified, then indicate so in the parameters by not having var there. Nim optimizes parameter passing into hidden pointer (T *) if it met certain criterias, which IIRC is that the type size is larger 3 times the size of float. You could manually enforce this by annotating your type with {.byref.} however.
IMO, the entire idea of const pointers in C centers around the fact that you have to manually optimize parameter passing, and a way to avoid accidental modifications is useful. But Nim is not C, so its semantics do not apply here.
If we need to define a callback function (type of a param is const T *) in Nim, using ABI would not help.
As of currently there's no way to mark a pointer in Nim as "look, but don't change", so your best bet is to just try not to modify anything. A hack like this should work though:
# Assuming that your type is passed by ref
type
O {.byref.} = object
proc callback(o: O) = discard
proc c_callback(po: ptr O) {.cdecl.} =
if not po.isNil: callback(po[])
While C won't see const T *, we can be certain that it won't be changed per Nim's semantics, so you can just ignore the warnings.
Ultimately for those compiler-specific attributes, that should be done by libraries once this is solved: https://github.com/nim-lang/Nim/issues/10682
I didn't notice bad assembly when not using const on params I think a bad assembly can only happen in the following case:
Both can be solved by making the actual implementation visible at call site, this is easily done by using {.inline.}.
I like to see that Nim could generate something similar to f(O *a, const O *b) for below f:
type
O {.byref.} = object ...
proc f*(a: var O; b: O): int = ...
We need a const/immutable ptr T to help us from crash, not just reading and remembering the signature of the C function:
{.emit: """
const int a = 1234;
const int *fconst(void) { return &a; }
"""
.}
proc myf(): ptr int {.importc: "fconst" nodecl.}
proc f(): int =
let p = myf()
p[] = 456
return p[]
P.S. C can't do X is not reason for Nim not to do Y.
C can't do X is not reason for Nim not to do Y.
Copying bad design is not good design.