Skip to content

Commit efb899d

Browse files
Allow using std::string_view instead of ErlNifBinary (#4)
Co-authored-by: Jonatan Kłosko <[email protected]>
1 parent 6f0dd2f commit efb899d

File tree

5 files changed

+56
-9
lines changed

5 files changed

+56
-9
lines changed

README.md

Lines changed: 10 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -138,6 +138,7 @@ Fine provides implementations for the following types:
138138
| `bool` | x | x |
139139
| `ErlNifPid` | x | x |
140140
| `ErlNifBinary` | x | x |
141+
| `std::string_view` | x | x |
141142
| `std::string` | x | x |
142143
| `fine::Atom` | x | x |
143144
| `std::nullopt_t` | x | |
@@ -169,9 +170,15 @@ Fine provides implementations for the following types:
169170
> talking about UTF-8 encoded strings or arbitrary binaries.
170171
>
171172
> However, when dealing with large binaries, it is preferable for the
172-
> NIF to accept `ErlNifBinary` as arguments and deal with the raw data
173-
> explicitly, which is zero-copy. That said, keep in mind that `ErlNifBinary`
174-
> is read-only and only valid during the NIF call lifetime.
173+
> NIF to accept `std::string_view` (or `ErlNifBinary`) as arguments and
174+
> deal with the raw data explicitly, which is zero-copy. That said,
175+
> keep in mind that those objects are read-only pointers to the data
176+
> and they are valid only during the NIF call lifetime.
177+
>
178+
> Since Elixir binaries are not zero-terminated, the use of
179+
> `std::string` is recommended when interfacing with C APIs, although
180+
> this will require an additional allocation, which cannot be
181+
> circumvented.
175182
>
176183
> Similarly, when returning large binaries, prefer creating the term
177184
> with `enif_make_new_binary` and returning `fine::Term`, as shown below.

include/fine.hpp

Lines changed: 30 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -5,16 +5,28 @@
55
#include <cstdint>
66
#include <cstdlib>
77
#include <cstring>
8-
#include <erl_nif.h>
98
#include <map>
109
#include <memory>
1110
#include <optional>
1211
#include <stdexcept>
1312
#include <string>
13+
#include <string_view>
1414
#include <type_traits>
1515
#include <variant>
1616
#include <vector>
1717

18+
#include <erl_nif.h>
19+
20+
#if defined(_MSVC_LANG)
21+
#define CPP_VERSION _MSVC_LANG
22+
#else
23+
#define CPP_VERSION __cplusplus
24+
#endif
25+
26+
#if CPP_VERSION < 201703L
27+
#error "elixir-nx/fine only supports C++ 17 and later"
28+
#endif
29+
1830
#if ERL_NIF_MAJOR_VERSION > 2 || \
1931
(ERL_NIF_MAJOR_VERSION == 2 && ERL_NIF_MINOR_VERSION >= 17)
2032
#define FINE_ERL_NIF_CHAR_ENCODING ERL_NIF_UTF8
@@ -396,11 +408,17 @@ template <> struct Decoder<ErlNifBinary> {
396408
}
397409
};
398410

411+
template <> struct Decoder<std::string_view> {
412+
static std::string_view decode(ErlNifEnv *env, const ERL_NIF_TERM &term) {
413+
auto binary = fine::decode<ErlNifBinary>(env, term);
414+
return std::string_view(reinterpret_cast<const char *>(binary.data),
415+
binary.size);
416+
}
417+
};
418+
399419
template <> struct Decoder<std::string> {
400420
static std::string decode(ErlNifEnv *env, const ERL_NIF_TERM &term) {
401-
auto binary = fine::decode<ErlNifBinary>(env, term);
402-
return std::string(
403-
{reinterpret_cast<const char *>(binary.data), binary.size});
421+
return std::string(fine::decode<std::string_view>(env, term));
404422
}
405423
};
406424

@@ -659,8 +677,8 @@ template <> struct Encoder<ErlNifBinary> {
659677
}
660678
};
661679

662-
template <> struct Encoder<std::string> {
663-
static ERL_NIF_TERM encode(ErlNifEnv *env, const std::string &string) {
680+
template <> struct Encoder<std::string_view> {
681+
static ERL_NIF_TERM encode(ErlNifEnv *env, const std::string_view &string) {
664682
ERL_NIF_TERM term;
665683
auto data = enif_make_new_binary(env, string.length(), &term);
666684
if (data == nullptr) {
@@ -671,6 +689,12 @@ template <> struct Encoder<std::string> {
671689
}
672690
};
673691

692+
template <> struct Encoder<std::string> {
693+
static ERL_NIF_TERM encode(ErlNifEnv *env, const std::string &string) {
694+
return fine::encode<std::string_view>(env, string);
695+
}
696+
};
697+
674698
template <> struct Encoder<Atom> {
675699
static ERL_NIF_TERM encode(ErlNifEnv *env, const Atom &atom) {
676700
if (atom.term) {

test/c_src/finest.cpp

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -102,6 +102,11 @@ ErlNifBinary codec_binary(ErlNifEnv *, ErlNifBinary term) {
102102
}
103103
FINE_NIF(codec_binary, 0);
104104

105+
std::string_view codec_string_view(ErlNifEnv *, std::string_view term) {
106+
return term;
107+
}
108+
FINE_NIF(codec_string_view, 0);
109+
105110
std::string codec_string(ErlNifEnv *, std::string term) { return term; }
106111
FINE_NIF(codec_string, 0);
107112

test/lib/finest/nif.ex

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -21,6 +21,7 @@ defmodule Finest.NIF do
2121
def codec_bool(_term), do: err!()
2222
def codec_pid(_term), do: err!()
2323
def codec_binary(_term), do: err!()
24+
def codec_string_view(_term), do: err!()
2425
def codec_string(_term), do: err!()
2526
def codec_atom(_term), do: err!()
2627
def codec_nullopt(), do: err!()

test/test/finest_test.exs

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -67,6 +67,16 @@ defmodule FinestTest do
6767
end
6868
end
6969

70+
test "string_view" do
71+
assert NIF.codec_string_view("hello world") == "hello world"
72+
assert NIF.codec_string_view(<<0, 1, 2>>) == <<0, 1, 2>>
73+
assert NIF.codec_string_view(<<>>) == <<>>
74+
75+
assert_raise ArgumentError, "decode failed, expected a binary", fn ->
76+
NIF.codec_string(1)
77+
end
78+
end
79+
7080
test "string" do
7181
assert NIF.codec_string("hello world") == "hello world"
7282
assert NIF.codec_string(<<0, 1, 2>>) == <<0, 1, 2>>

0 commit comments

Comments
 (0)