proc asString*(sc: seq[char]): cstring =
cast[string] (sc)
proc asString*(sc: ptr seq[char]): cstring =
asString(sc[])
proc asString*(sc: openArray[char]): cstring =
cast[cstring] (unsafeAddr (sc))
proc copyToSeq*(s: string): seq[char] =
cast[seq[char]] (s)
template asSeq*(s: string): ptr seq[char] =
# fails as a proc
cast[ptr seq[char]] (addr(s))
when isMainModule:
var s = "12345"
var cst = cstring(s)
var cs = copyToSeq(s) # cs is a copy
var sx = asSeq(s)
s[4] = '6'
assert cst.len == s.len
assert sx[] == @['1', '2', '3', '4', '6']
assert cs == @['1', '2', '3', '4', '5']
assert cst == "12346"
let css = asString(cs)
let pcs = addr(cs)
cs[0] = '6'
assert css == "62345"
assert css.len == 5
assert asString(pcs) == "62345"
echo "all good"
let oa = asString(toOpenArray(cs, 0, len(cs) - 2))
assert oa == "6234"
# fails: oa = "62345"
assert len(oa) == 4
# fails: len(oa) = 5
I realise this is all a bit of a hack. Is there a way to have
proc asString*(sc: openArray[char])
return a string of the correct length without copying?See: https://github.com/status-im/nim-http-utils/pull/9
Lastly, procedures that accepts openarray[char] also accept strings as a special case so there is no need to convert.
Regarding your proc:
proc copyToSeq*(s: string): seq[char] =
cast[seq[char]] (s)
Use a template instead, the result is a seq types this will trigger the GC for init of the result and then will place the cast in that temporary variable but due to value semantics it will need to create a copy. Casting string to seq[char] or seq[byte] or even seq[uint8] is OK from a representation point of view, the fact that the string as an extra '0' will not cause problem in that direction of conversion/cast.
That said I strongly suggest that you use seq[byte] for binary blobs and string for human strings.
So I am just putting this here for posterity.
As it turns out, cstrings can be used for string slicing. This proc will return a view of the string from index first to the end.
proc strSliceFrom*(instr: string, first: int): cstring {.inline.} =
cast[cstring] (unsafeAddr(instr[first]))
If one can accept that the input buffer will be modified, this second proc will produce a slice of the correct size, for as long as the inserted '\0' character is not subsequently removed.
proc strSlice*(instr: var string, first, last: int): cstring {.inline.} =
instr[last] = '\0'
strSliceFrom(instr, first)
A possible use case is parsing csv or tsv strings, where the delimiters and line ends can be changed to '\0'.
#slicing
var instr: string = "1,2\n,3,4\n"
let tmp = strSlice(instr, 0, 3)
instr[0] = '5'
assert instr == "5,2\0,3,4\n"
assert tmp == "5,2"
assert len(tmp) == 3
instr[3] = '\n'
assert tmp == "5,2\n,3,4\n"
assert len(tmp) == 9