For example, what would be Nim analogue for the following C code:
typedef struct BoxOfStuff * pBox;
/* no need to include file where contents of BoxOfStuff is
* defined since here we don't care */
struct Warehouse {
pBox shelf[]; /* here we want compiler to ensure type of items is pBox */
}
@ivanitto: I agree with Varriount that it's best to declare the types in Nim along with the correct C type pragmas. You don't have to define the entire type, just enough to indicate its semantics to Nim and to identify it in the C type system:
type
BoxOfStuff {.final, incompleteStruct, header: "<BoxOfStuff.h>", importc: "BoxOfStuff".} = object
PBox {.header: "<BoxOfStuff.h>", importc: "pBox".} = ptr BoxOfStuff
Warehouse {.header: "<Warehouse.h>", importc: "Warehouse".} = object
shelf: ptr PBox
If you have no other choice, you can (ab)use C's type system, and treat Warehouse as its C ABI type, which is just a pointer:
type
WarehouseUnsafeDontDoThis {.distinct.} = pointer
This will not work if you have C++ linkage though.
The question is not about interfacing with other languages, but exceptionally about interfacing of Nim modules with each other. The C language allows to hide internals of a type declaration but still have type checking using just "half-anonymous" struct BoxOfStuff * pointer. I want this functionality in Nim. And even more: I want to declare "half-anonymous" reference instead of pointer.
I use "half-anonymous" term to distinguish from "fully-anonymous" pointer which is simply void*.
Here what I figured out so far, using "fully-anonymous" pointers in Nim:
warehouse.nim:
# Does Nim allow to declare half-anonymous pointer like in 'C' below?
# typedef struct BoxOfStuff * pBox;
# no need to include file where contents of BoxOfStuff is
# defined since here we don't care
#struct Warehouse {
# pBox shelf[]; #c-compiler ensures type of items is pBox. What about Nim?
#}
type
Warehouse = object
shelf : seq[pointer] # I want anonymous references with type checking here. How?
rWarehouse = ref Warehouse
box_of_eggs.nim:
import warehouse
# this file is not imported by warehouse.nim
type
BoxOfEggs = object
# some stuff
rBoxOfEggs = ref BoxOfEggs
# example push/pop functions allowing type checking:
#
proc pushbox* (w:rWarehouse, b:rBoxOfEggs) {.inline.} =
GC_ref(b) # NOTE: it will crash without it, because we assign into anonymous ptr
w.shelf.add(cast[pointer](b))
proc popbox* (w:rWarehouse) : rBoxOfEggs {.inline.} =
result = cast[rBoxOfEggs](w.shelf.pop)
GC_unref(result)
I have still some trouble understanding your intended use case. Nim is a high level language, so low level stuff like pointers, cast, addr, GC_ref and GC_unref is generally only necessary if interfacing with low level C libraries, when at all. (OK, it may be also needed when you write real low level code in Nim, for example kernels or hardware depending device drivers.)
For exported symbols Nim uses the star marker *, as Modula or Oberon did already many years ago. For exported objects, you mark the object type itself and all of its exported fields with as star marker. If no fields are marked with *, you get a fully opaque type. Of course Nim compiler still does full type checking, as long you do not use low level stuff as cast and addr. And the later is nearly never needed in pure Nim. For export marker see manual https://nim-lang.org/docs/manual.html#procedures-export-marker
I come from a different school of languages so I would perhaps do something like the following:
warehouse.nim:
type
# We want everything on the shelf to be a Box
Box* = ref object of RootObj
Warehouse* = ref object
shelf: seq[Box] # We do not use * to export the shelf
# IMHO these procs should be in this module, not the other way around,
# encapsulation is better.
proc pushbox*[T: Box](w: Warehouse, b: T) =
w.shelf.add(b)
# Fiddling with [T] here didn't fly which I suspect has to do with
# the simple fact that we don't know what is coming out from the
# seq until at runtime.
proc popbox*(w: Warehouse): Box =
w.shelf.pop
# Nim style constructor proc
proc newWarehouse*(): Warehouse =
Warehouse(shelf: newSeq[Box]())
box.nim:
import warehouse
type
BoxOfEggs = ref object of Box
number: int
BoxOfBananas = ref object of Box
color: string
Banana = ref object of RootObj
var eggs = BoxOfEggs()
var bananas = BoxOfBananas()
var banana = Banana()
var warehouse = newWarehouse()
warehouse.pushbox(eggs)
warehouse.pushbox(bananas)
# Below line will not compile:
# Error: type mismatch: got (Warehouse, Banana)
# but expected one of:
# proc pushbox[T: Box](w: Warehouse; b: T)
#
#warehouse.pushbox(banana)
# Here we get a Box out of popbox, but the runtime Conversion is type safe
# so will cause a runtime ObjectConversionError if it's an BoxOfEggs or vice versa
var myBananas: BoxOfBananas
myBananas = BoxOfBananas(warehouse.popbox())
var myEggs: BoxOfEggs
myEggs = warehouse.popbox().BoxOfEggs # Different syntax style
# Below line will not compile:
# type mismatch: got (Box) but expected 'Banana = ref Banana:ObjectType'
#var myBanana: Banana = Banana(warehouse.popbox())
You don't export what you don't want to export. You cannot export what you don't mark it with *.
You can export the pBox but don't export the implementation BoxOfStuff and Nim still happily checks whether the type is consistent or not.
Also you can limit imported symbol using from module with
from warehouse import pBox