nim-lang / Nim

Nim is a statically typed compiled systems programming language. It combines successful concepts from mature languages like Python, Ada and Modula. Its design focuses on efficiency, expressiveness, and elegance (in that order of priority).
https://nim-lang.org
Other
16.51k stars 1.46k forks source link

`const char*` #19588

Open sls1005 opened 2 years ago

sls1005 commented 2 years ago

const char* is returned by some C/C++ functions, but it (usually) can't be represented in Nim. Sometimes we use cstring for it, which may cause problems because many C++ compilers insist that they're not the same thing.

Example

{.emit: """
const char* foo() {
  return "hello";
}
""".}
proc foo(): cstring {.importcpp.}
echo foo()

Current Output

error: cannot initialize a variable of type 'NCSTRING' (aka 'char *')
with an rvalue of type 'const char *'
        NCSTRING T2_ = foo();
                 ^     ~~~~~
1 error generated.

Possible Solution

Additional Information

I've tried cast[cstring](foo()), or even cast[ptr char](foo()), both showed me the same error.

$ nim -v
Nim Compiler Version 1.6.0 [Android: arm64]
Compiled at 2021-10-19
Copyright (c) 2006-2021 by Andreas Rumpf

git hash: 727c6378d2464090564dbcd9bc8b9ac648467e38
active boot switches: -d:release -d:danger
ringabout commented 2 years ago

works for C backend

type
  cstringConstImpl {.importc:"const char*".} = cstring
  constChar* = distinct cstringConstImpl

{.emit: """
const char* foo() {
  return "hello";
}
""".}
proc foo(): constChar {.importc.} # change to importcpp for C++ backend
echo foo().cstring

ref https://dev.to/xflywind/wrap-const-char-in-the-nim-language-53no

juancarlospaco commented 2 years ago

Maybe that @xflywind examples can be in the official documentation somewhere (?) 🤔

dmknght commented 1 year ago

I think cstring is compatible with const char * on Nim 1.6.2.

Nim info:

Nim Compiler Version 1.6.2 [Linux: amd64]
Compiled at 2023-01-11
Copyright (c) 2006-2021 by Andreas Rumpf

I'm doing a binding of libtlsh-dev. A part of the binding is:

{.pragma: impTlsh, header: "tlsh.h".}
{.passL: "-ltlsh".}

type
  Tlsh {.importcpp: "Tlsh".} = object

# A "hack" to create "new" binding
# https://nim-lang.org/docs/manual.html#implementation-specific-pragmas-importcpp-pragma
proc tlsh_new*[T](x: T): ptr T {.importcpp: "(new '*0#@)", nodecl.}
proc TlshObj*(): Tlsh {.importcpp: "Tlsh(@)".}
proc tlsh_free*(tlsh: ptr Tlsh) {.importcpp: "#.~Tlsh()".}

# Binding method
proc version*(tlsh: ptr Tlsh): cstring {.importcpp, impTlsh.}

# Testing code
var
  vtlsh = tlsh_new TlshObj()

echo vtlsh.version()

When I compiled with cpp and run, I got output: 3.4.4 compact hash 1 byte checksum

So I assume it's okay Meanwhile I'm having hard time trying to pass const char * to input.

I have a method const char* getHash(char *buffer, unsigned int bufSize) const;, which I generated the binding proc getHash*(tlsh: ptr Tlsh, buffer: cstring, size: uint): cstring {.importcpp, impTlsh.}. I compiled the code and I didn't see any invalid conversion but the program crashed.

dmknght commented 1 year ago

(I deleted previous comment because I was wrong doing the code) The cstring when compile nim cpp is compatible with const char * The test of @sls1005 worked on my side Also my test with Cpp binding worked fine. The code

var
  t1: Tlsh
  str1: cstring = "Test string hello world"

t1.final(str1, 512)
echo t1.getHash()
echo t1.version()

-> The method that uses const char* is perfectly fine. (in previous test, i used the wrong buffer size so the data was empty)

sls1005 commented 1 year ago

Really? I'm still getting

error: cannot initialize a variable of type 'NCSTRING' (aka 'char *') with an rvalue of type 'const char *'
        NCSTRING T2_ = foo();
                 ^     ~~~~~
1 error generated.

with Nim 1.6.14.

Maybe my backend C++ compiler is more strict.

daniel-j commented 1 year ago

Getting the same error and @ringabout's example fails with cpp backend when inside the Raspberry Pi Pico SDK, but works when running nim cpp -r example.nim. Probably because Nim calls the compiler here, while using the SDK calls g++ from CMake. I had to modify the example to add nodecl pragma to proc foo.

Noticed it when wrapping JPEGDEC and trying out the cpp backend.

Output from just throwing the snippet into a random file I was working on:

/usr/bin/arm-none-eabi-g++ -DCFG_TUSB_MCU=OPT_MCU_RP2040 -DCFG_TUSB_OS=OPT_OS_PICO -DCYW43_LWIP=1 -DLIB_PICO_ASYNC_CONTEXT_THREADSAFE_BACKGROUND=1 -DLIB_PICO_BIT_OPS=1 -DLIB_PICO_BIT_OPS_PICO=1 -DLIB_PICO_CYW43_ARCH=1 -DLIB_PICO_DIVIDER=1 -DLIB_PICO_DIVIDER_HARDWARE=1 -DLIB_PICO_DOUBLE=1 -DLIB_PICO_DOUBLE_PICO=1 -DLIB_PICO_FIX_RP2040_USB_DEVICE_ENUMERATION=1 -DLIB_PICO_FLOAT=1 -DLIB_PICO_FLOAT_PICO=1 -DLIB_PICO_INT64_OPS=1 -DLIB_PICO_INT64_OPS_PICO=1 -DLIB_PICO_MALLOC=1 -DLIB_PICO_MEM_OPS=1 -DLIB_PICO_MEM_OPS_PICO=1 -DLIB_PICO_PLATFORM=1 -DLIB_PICO_PRINTF=1 -DLIB_PICO_PRINTF_PICO=1 -DLIB_PICO_RAND=1 -DLIB_PICO_RUNTIME=1 -DLIB_PICO_STANDARD_LINK=1 -DLIB_PICO_STDIO=1 -DLIB_PICO_STDIO_USB=1 -DLIB_PICO_STDLIB=1 -DLIB_PICO_SYNC=1 -DLIB_PICO_SYNC_CRITICAL_SECTION=1 -DLIB_PICO_SYNC_MUTEX=1 -DLIB_PICO_SYNC_SEM=1 -DLIB_PICO_TIME=1 -DLIB_PICO_UNIQUE_ID=1 -DLIB_PICO_UTIL=1 -DMBEDTLS_CONFIG_FILE=\"mbedtls_config.h\" -DPICO_BOARD=\"pico_w\" -DPICO_BUILD=1 -DPICO_CMAKE_BUILD_TYPE=\"Release\" -DPICO_COPY_TO_RAM=0 -DPICO_CXX_ENABLE_EXCEPTIONS=1 -DPICO_CYW43_ARCH_THREADSAFE_BACKGROUND=1 -DPICO_NO_FLASH=0 -DPICO_NO_HARDWARE=0 -DPICO_ON_DEVICE=1 -DPICO_RP2040_USB_DEVICE_UFRAME_FIX=1 -DPICO_STDIO_USB_CONNECT_WAIT_TIMEOUT_MS=0 -DPICO_TARGET_NAME=\"inky_frame_slideshow\" -DPICO_USE_BLOCKED_RAM=0 -I/home/djazz/.choosenim/toolchains/nim-2.0.0/lib -I/home/djazz/code/nim/nim-pimoroni-pico/examples -I/home/djazz/code/nim/nim-pimoroni-pico/examples/../csource -I/usr/share/pico-sdk/src/rp2_common/hardware_claim/include -I/usr/share/pico-sdk/src/common/pico_base/include -I/home/djazz/code/nim/nim-pimoroni-pico/build/examples/generated/pico_base -I/usr/share/pico-sdk/src/boards/include -I/usr/share/pico-sdk/src/rp2_common/pico_platform/include -I/usr/share/pico-sdk/src/rp2040/hardware_regs/include -I/usr/share/pico-sdk/src/rp2_common/hardware_base/include -I/usr/share/pico-sdk/src/rp2040/hardware_structs/include -I/usr/share/pico-sdk/src/rp2_common/hardware_sync/include -I/usr/share/pico-sdk/src/rp2_common/hardware_timer/include -I/usr/share/pico-sdk/src/rp2_common/hardware_irq/include -I/usr/share/pico-sdk/src/common/pico_sync/include -I/usr/share/pico-sdk/src/common/pico_time/include -I/usr/share/pico-sdk/src/common/pico_util/include -I/usr/share/pico-sdk/src/rp2_common/pico_rand/include -I/usr/share/pico-sdk/src/rp2_common/pico_unique_id/include -I/usr/share/pico-sdk/src/rp2_common/hardware_flash/include -I/usr/share/pico-sdk/src/rp2_common/pico_bootrom/include -I/usr/share/pico-sdk/src/rp2_common/hardware_clocks/include -I/usr/share/pico-sdk/src/rp2_common/hardware_gpio/include -I/usr/share/pico-sdk/src/rp2_common/hardware_resets/include -I/usr/share/pico-sdk/src/rp2_common/hardware_pll/include -I/usr/share/pico-sdk/src/rp2_common/hardware_vreg/include -I/usr/share/pico-sdk/src/rp2_common/hardware_watchdog/include -I/usr/share/pico-sdk/src/rp2_common/hardware_xosc/include -I/usr/share/pico-sdk/src/common/pico_stdlib/include -I/usr/share/pico-sdk/src/rp2_common/hardware_uart/include -I/usr/share/pico-sdk/src/rp2_common/hardware_divider/include -I/usr/share/pico-sdk/src/rp2_common/pico_runtime/include -I/usr/share/pico-sdk/src/rp2_common/pico_printf/include -I/usr/share/pico-sdk/src/common/pico_bit_ops/include -I/usr/share/pico-sdk/src/common/pico_divider/include -I/usr/share/pico-sdk/src/rp2_common/pico_double/include -I/usr/share/pico-sdk/src/rp2_common/pico_float/include -I/usr/share/pico-sdk/src/rp2_common/pico_malloc/include -I/usr/share/pico-sdk/src/common/pico_binary_info/include -I/usr/share/pico-sdk/src/rp2_common/pico_stdio/include -I/usr/share/pico-sdk/src/rp2_common/pico_stdio_usb/include -I/usr/share/pico-sdk/src/common/pico_usb_reset_interface/include -I/usr/share/pico-sdk/src/rp2_common/pico_int64_ops/include -I/usr/share/pico-sdk/src/rp2_common/pico_mem_ops/include -I/usr/share/pico-sdk/src/rp2_common/boot_stage2/include -I/usr/share/pico-sdk/lib/tinyusb/src -I/usr/share/pico-sdk/lib/tinyusb/src/common -I/usr/share/pico-sdk/lib/tinyusb/hw -I/usr/share/pico-sdk/src/rp2_common/pico_fix/rp2040_usb_device_enumeration/include -I/usr/share/pico-sdk/lib/lwip/src/include -I/usr/share/pico-sdk/src/rp2_common/hardware_rtc/include -I/usr/share/pico-sdk/src/rp2_common/hardware_pwm/include -I/home/djazz/code/nim/nim-pimoroni-pico/build/examples/pimoroni_pico_vendor/sdcard -I/home/djazz/code/nim/nim-pimoroni-pico/src/pimoroni_pico/vendor/sdcard -I/home/djazz/code/nim/nim-pimoroni-pico/src/pimoroni_pico/vendor/fatfs -I/usr/share/pico-sdk/src/rp2_common/hardware_spi/include -I/usr/share/pico-sdk/src/rp2_common/hardware_pio/include -I/usr/share/pico-sdk/src/rp2_common/hardware_adc/include -I/usr/share/pico-sdk/src/rp2_common/pico_async_context/include -I/usr/share/pico-sdk/src/rp2_common/pico_lwip/include -I/usr/share/pico-sdk/src/rp2_common/pico_cyw43_arch/include -I/usr/share/pico-sdk/lib/cyw43-driver/src -I/usr/share/pico-sdk/lib/cyw43-driver/firmware -I/usr/share/pico-sdk/src/rp2_common/pico_cyw43_driver/cybt_shared_bus -I/usr/share/pico-sdk/src/rp2_common/hardware_dma/include -I/usr/share/pico-sdk/src/rp2_common/hardware_exception/include -I/usr/share/pico-sdk/src/rp2_common/pico_cyw43_driver/include -I/home/djazz/code/nim/nim-pimoroni-pico/build/examples/pico-sdk/src/rp2_common/pico_cyw43_driver -I/usr/share/pico-sdk/src/rp2_common/hardware_i2c/include -I/usr/share/pico-sdk/lib/mbedtls/include -I/usr/share/pico-sdk/lib/mbedtls/library -mcpu=cortex-m0plus -mthumb -O3 -DNDEBUG -std=gnu++17 -ffunction-sections -fdata-sections -fno-rtti -fno-use-cxa-atexit -w -MD -MT CMakeFiles/inky_frame_slideshow.dir/inky_frame_slideshow/nimcache/@m..@s..@ssrc@spimoroni_pico@slibraries@spico_graphics@sdrawjpeg.nim.cpp.obj -MF CMakeFiles/inky_frame_slideshow.dir/inky_frame_slideshow/nimcache/@m..@s..@ssrc@spimoroni_pico@slibraries@spico_graphics@sdrawjpeg.nim.cpp.obj.d -o CMakeFiles/inky_frame_slideshow.dir/inky_frame_slideshow/nimcache/@m..@s..@ssrc@spimoroni_pico@slibraries@spico_graphics@sdrawjpeg.nim.cpp.obj -c /home/djazz/code/nim/nim-pimoroni-pico/build/examples/inky_frame_slideshow/nimcache/@m..@s..@ssrc@spimoroni_pico@slibraries@spico_graphics@sdrawjpeg.nim.cpp
/home/djazz/code/nim/nim-pimoroni-pico/build/examples/inky_frame_slideshow/nimcache/@m..@s..@ssrc@spimoroni_pico@slibraries@spico_graphics@sdrawjpeg.nim.cpp: In function 'void atmdotdotatsdotdotatssrcatspimoroni_picoatslibrariesatspico_graphicsatsdrawjpegdotnim_Init000()':
/home/djazz/code/nim/nim-pimoroni-pico/build/examples/inky_frame_slideshow/nimcache/@m..@s..@ssrc@spimoroni_pico@slibraries@spico_graphics@sdrawjpeg.nim.cpp:1829:35: error: invalid conversion from 'const char*' to 'NCSTRING' {aka 'char*'} [-fpermissive]
 1829 |         colontmpD_ = cstrToNimstr(T4_);
      |                                   ^~~
      |                                   |
      |                                   const char*
/home/djazz/code/nim/nim-pimoroni-pico/build/examples/inky_frame_slideshow/nimcache/@m..@s..@ssrc@spimoroni_pico@slibraries@spico_graphics@sdrawjpeg.nim.cpp:477:61: note:   initializing argument 1 of 'NimStringV2 cstrToNimstr(NCSTRING)'
  477 | N_LIB_PRIVATE N_NIMCALL(NimStringV2, cstrToNimstr)(NCSTRING str_p0);
      |                                                    ~~~~~~~~~^~~~~~

Since JPEGDEC's files is in my repo I could simply remove const from the offending line in the header file.

jmgomez commented 1 year ago

Unfortunately, I dont think we are going to ever see const semantics working in Nim. But for anyone coming here and using the C++ backend, member (https://nim-lang.github.io/Nim/manual_experimental.html#member-pragma) (Nim 2.2 or devel) is the way to go about it. The last example shows how to implement a functor with it

sls1005 commented 9 months ago

This seems to work perfectly on both C and C++ backends:

type ConstCString* {.importc: "const char*".} = object

converter toCString*(self: ConstCString): cstring {.importc: "(char*)", noconv, nodecl.}
converter toConstCString*(self: cstring): ConstCString {.importc: "(const char*)", noconv, nodecl.}
proc `$`*(self: ConstCString): string = $(self.toCString())

{.emit: """
#ifdef __cplusplus
  extern "C"
#endif
const char * f() {
    return "123";
}
""".}

proc f(): ConstCString {.importc.}

var s: cstring = f()
echo s #123
ringabout commented 7 months ago

@daniel-j

Noticed it when wrapping JPEGDEC and trying out the cpp backend. Output from just throwing the snippet into a random file I was working on:

clearly some compilers are more strict on const char* (such as clang etc.). I'm going to open a pull request which should add a conversion for cstrToNimstr; see also https://github.com/nim-lang/Nim/issues/12703 => https://github.com/nim-lang/Nim/pull/23371