Hey everyone! I'm still learning about the nim type system, and came across this situation where things don't work as expected. Maybe someone can help explain what I'm doing wrong. Here's a simplified version of my code which causes the problem:
import strformat
type
DfuRequest = array[4, uint8]
DfuResponse = array[4, uint8]
func data(req: DfuRequest | DfuResponse): int32 =
req[1].int32 + (req[2].int32 shl 8)
func status(resp: DfuResponse): string =
case resp[0]:
of 0x5a'u8: "ack"
of 0xa4'u8: "nack"
else: "unknown"
proc `$`(req: DfuRequest): string =
result = fmt"DFU Request: {req[0]}, 0x{req.data:04x}, 0x{req[^1]:02x}"
proc `$`(resp: DfuResponse): string =
result = fmt"DFU Response: {resp.status}, 0x{resp.data:04x}, 0x{resp[^1]:02x}"
let req: DfuRequest = [0x01, 0x12, 0x34, 0]
echo($(req))
When compiling with nim compiler version 2.2.2 I get the following error: Error: ambiguous call; both test.$(req: DfuRequest) [...] and test.$(resp: DfuResponse) [...] match for: (DfuRequest)
2. If so, is it somehow possible to achieve what I'm wanting to do using the nim type system? Specifically, I want to treat DfuRequest and DfuResponse as different types even though they share the same underlying storage. I believe this makes my code cleaner because it explicitly keeps track of which kind of data each proc works with. Thanks in advance!
@stoking, thanks for the response and indeed the distinct type makes my code from above work. I edited the type statement and implemented [] and []= as follows:
type
BufferT = array[4, uint8]
DfuRequest = distinct BufferT
DfuResponse = distinct BufferT
proc `[]`(a: DfuRequest | DfuResponse, i: Ordinal | BackwardsIndex): uint8 =
result = a.BufferT[i]
proc `[]=`(a: DfuRequest | DfuResponse, i: Ordinal | BackwardsIndex, x: uint8) =
a.BufferT[i] = x
But is it possible to use .{borrow}. somehow here? Everything I tried game me a Error: borrow from proc return type mismatch: 'T' compiler error. Thanks again! @stoking, after reading more about distinct types I don't know if that's what I really need in this case, although using a distinct type does make the code work. From the manual:
Distinct type
A distinct type is a new type derived from a base type that is incompatible with its base type. In particular, it is an essential property of a distinct type that it does not imply a subtype relation between it and its base type.
I'm not sure that I need a type that is incompatible with its base type. I still want a DfuRequest to act like an array. I just want to be able to define a different proc to work with a DfuRequest than with a DfuResponse. So does anyone know if there's a way to accomplish this without resorting to distinct types?
I'm also confused about how exactly an array type is treated. Looking at the source code of system.nim (lines 373 to 376)[https://github.com/nim-lang/Nim/blob/58f1e22db352ed3cff0f9303acf064a291c25d8d/lib/system.nim#L373C1-L376C47] shows array access as proc `[]`*[I: Ordinal;T](a: T; i: I): T ... which would imply that an array access of an array of type T would also return the same type T? So is an array of type array[4, uint8] the same type as uint8?
One approach you can use is to make them object types instead, with an array field:
type
DfuRequest = object
bytes: array[4, uint8]
DfuResponse = object
bytes: array[4, uint8]
proc `[]`(r: DfuRequest | DfuResponse, i: int | BackwardsIndex): uint8 {.inline.} = r.bytes[i]
proc `[]=`(r: DfuRequest | DfuResponse, i: int | BackwardsIndex, val: uint8) {.inline.} = r.bytes[i] = val
Note that in this case you need to use object construction, not array construction:
let req = DfuRequest(bytes: [0x01, 0x12, 0x34, 0])
Mmm okay, I think I understand. When these built-in types are named, they're just considered structurally. If you want discrimination based on name you have to mark it distinct.
Incidentally, I don't think this is how Go works (I'm not saying Nim has a bad design, I'm just trying to understand it),
type Type1 []int
type Type2 []int
func f(type1 Type1) string {
return "hi"
}
var t2 Type2 = []int{1, 2, 3}
var a = f(t2)
Will not compile with "cannot use t2 (variable of slice type Type2) as Type1 value in argument to f"
Maybe there's some reason this isn't an apples to apples comparison though(?)
4. I still can't figure out how to {.borrow.} from system's []. So I need to define my own procs like in the example. Not a big deal but it bugs me that I don't understand the type system better so I would know how to use {.borrow.} instead. After reading the manual for a while I found
type array*[I, T]{.magic: "Array".}
and
# :array|openArray|string|seq|cstring|tuple
proc `[]`*[I: Ordinal;T](a: T; i: I): T {.noSideEffect, magic: "ArrGet".}
so after seeing this example in the manual
type Matrix[T, Rows, Columns] = object
...
proc `[]`(m: Matrix, row, col: int): Matrix.T =
m.data[col * high(Matrix.Columns) + row]
I was thinking something like
proc`[]`(a: DfuRequest; i: Ordinal): DfuRequest.T {.borrow.}
would work, but I get the error Error: undeclared field: 'T' So I'm going to keep thinking about this and if anyone knows how this would work please feel free to chime in!
Thanks!
proc`[]`(a: DfuRequest; i: Ordinal): DfuRequest.T {.borrow.}
Doesn't work because DfuRequest is not a generic type. The ".T" at the end is a marker for an instantiated generic type.
Borrow from [] is not supported. :(
Incidentally, I don't think this is how Go works ...
Correct in Golang type aliases have been changed to introduce a subtype relation. This saves one keyword ("type" vs "subtype") and thus upholds the illusion of "simplicity". Because nobody ever found a better way to judge the complexity of a programming language than by counting its keywords. Pragmas, of course, also count as keywords but thankfully semantic comments like //nostacksplit do not...