nandi/rustnimpublic Fork 0
7db7991
Commits
Clone
git clone https://git.rickub.com/nandi/rustnim.git
git clone ssh://git@rickub.com/nandi/rustnim.git

Host key fingerprint (ed25519): SHA256:iycHnxEyq0Q7uyVpB7JlznP0G7JrTPXLYRcAU5CSLhc — verify it before your first connect.

Lower `extern "C"` blocks to Nim importc; do not transpile libc

libc is third by dependents in libcosmic's tree but is not a crate to
translate. Measured: 129,594 lines across 387 files, of which 54,544 are
constants, 7,660 are extern "C" declarations and 1,926 are type aliases,
against 121 actual function bodies in the entire crate. There is no code in
it, and Nim reaches the same symbols natively -- they are the same objects,
not two implementations of one idea.

So the general capability was built instead. An extern "C" block lowers to
Nim importc declarations; both are statements about a symbol someone else
defines and both are bound by the C ABI, so the two declarations describe one
symbol rather than one being a translation of the other. Raw pointers map as
well, which cleared all 38 raw-pointer blockers in the 400-crate survey.

The interesting part is const. Rust emits no C prototype and Nim emits a real
one, so declaring strlen as taking *const u8 gets "conflicting types for
'strlen'; have 'NU(char *)'" against string.h -- the C compiler catching a
declaration that does not match the symbol, which is a better outcome than
Rust's silence. To let honest declarations be expressed, *const T maps to a
generated const-qualified alias, one per element type used. Nim's generic
form (importc: "const $1*") was tried first and does not work in 2.2.4. A
*const T whose C spelling we do not know is rejected rather than quietly
declared without the const.

Also: as_ptr/as_mut_ptr, pointer and integer-to-pointer casts, the C type
aliases (c_int and friends, which Nim has under its own names and which are
defined by the same platform compiler), and unsafe blocks in trailing
expression position.

tests/cases/036-extern-c.rs calls abs, labs, strlen and atoi through this
path, byte-identical to rustc. 42 differential cases, all green.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
nandithebull committed 2026-09-18T22:59:39-07:00 Browse files
7db7991 parent: 12c0a01
modified DESIGN.md +52 -2
@@ -304,8 +304,7 @@ runner) rather than a wrong answer.
304304 ### Still open
305305
306306 6. Const generic parameters, `move` closures, closure bodies with statements,
307- trait objects, `macro_rules!` definitions and raw pointers are rejected
308- with a reason. A `trait` declaration lowers to nothing — trait resolution
307+ trait objects and `macro_rules!` definitions are rejected with a reason. A `trait` declaration lowers to nothing — trait resolution
309308 is not modelled — but one giving a method a *default body* is rejected,
310309 since that body is code with no impl to be emitted into.
311310 Lifetime parameters are *not* a rejection: they carry no runtime meaning
@@ -559,6 +558,57 @@ and `LevelFilter`. The first attempt used Rust's names and broke six existing
559558 cases, because a crate's own `Error` type and an enum field named `Error`
560559 cannot coexist in one Nim module.
561560
561+## `libc`: not transpiled, and should not be
562+
563+`libc` is third by dependents in `libcosmic`'s tree (60 of 741), but it is not
564+a crate to translate. Measured:
565+
566+```
567+129,594 lines across 387 files
568+ 54,544 `pub const`
569+ 7,660 `pub fn`, nearly all inside `extern "C"` blocks -- declarations
570+ 1,926 `pub type`
571+ 121 actual function bodies in the entire crate
572+```
573+
574+There is essentially no code in it. It is a set of declarations binding to the
575+platform's C library, and **Nim reaches those same symbols natively** — the
576+symbols are the same objects, not two implementations of one idea.
577+
578+So what was built is the general capability instead: an `extern "C"` block
579+lowers to Nim `importc` declarations. Both are statements *about* a symbol
580+someone else defines, and both are bound by the C ABI, so the two declarations
581+describe one symbol rather than one being a translation of the other. Raw
582+pointers map too (`*mut T` to `ptr T`), which cleared all 38 raw-pointer
583+blockers in the survey.
584+
585+### `const` matters at the C level even though it does not at the Rust one
586+
587+Rust emits no C prototype; Nim emits a real one. So declaring `strlen` as
588+taking `*const u8` produces:
589+
590+```
591+error: conflicting types for 'strlen'; have 'NU(char *)'
592+note: previous declaration with type 'size_t(const char *)'
593+```
594+
595+which is the C compiler catching a declaration that does not match the symbol
596+— a loud failure, and a better outcome than Rust's silence. To make honest
597+declarations expressible, `*const T` maps to a generated const-qualified
598+alias:
599+
600+```nim
601+type RsConstPtrcchar* {.importc: "const char *", nodecl.} = distinct pointer
602+proc strlen*(s: RsConstPtrcchar): uint {.importc: "strlen", cdecl.}
603+```
604+
605+one per element type actually used. Nim's generic form (`importc: "const $1*"`)
606+was tried first and does not work in 2.2.4. A `*const T` whose C spelling we
607+do not know is rejected rather than declared without the `const`.
608+
609+`tests/cases/036-extern-c.rs` calls `abs`, `labs`, `strlen` and `atoi` through
610+this path, byte-identical to rustc.
611+
562612 ## Proof of byte-identity for `base16ct`
563613
564614 [`PROOF.md`](PROOF.md) sets out what is actually established: exhaustive
@@ -304,8 +304,7 @@ runner) rather than a wrong answer.
304 ### Still open304 ### Still open
305 305
306 6. Const generic parameters, `move` closures, closure bodies with statements,306 6. Const generic parameters, `move` closures, closure bodies with statements,
307- trait objects, `macro_rules!` definitions and raw pointers are rejected307+ trait objects and `macro_rules!` definitions are rejected with a reason. A `trait` declaration lowers to nothing — trait resolution
308- with a reason. A `trait` declaration lowers to nothing — trait resolution
309 is not modelled — but one giving a method a *default body* is rejected,308 is not modelled — but one giving a method a *default body* is rejected,
310 since that body is code with no impl to be emitted into.309 since that body is code with no impl to be emitted into.
311 Lifetime parameters are *not* a rejection: they carry no runtime meaning310 Lifetime parameters are *not* a rejection: they carry no runtime meaning
@@ -559,6 +558,57 @@ and `LevelFilter`. The first attempt used Rust's names and broke six existing
559 cases, because a crate's own `Error` type and an enum field named `Error`558 cases, because a crate's own `Error` type and an enum field named `Error`
560 cannot coexist in one Nim module.559 cannot coexist in one Nim module.
561 560
561+## `libc`: not transpiled, and should not be
562+
563+`libc` is third by dependents in `libcosmic`'s tree (60 of 741), but it is not
564+a crate to translate. Measured:
565+
566+```
567+129,594 lines across 387 files
568+ 54,544 `pub const`
569+ 7,660 `pub fn`, nearly all inside `extern "C"` blocks -- declarations
570+ 1,926 `pub type`
571+ 121 actual function bodies in the entire crate
572+```
573+
574+There is essentially no code in it. It is a set of declarations binding to the
575+platform's C library, and **Nim reaches those same symbols natively** — the
576+symbols are the same objects, not two implementations of one idea.
577+
578+So what was built is the general capability instead: an `extern "C"` block
579+lowers to Nim `importc` declarations. Both are statements *about* a symbol
580+someone else defines, and both are bound by the C ABI, so the two declarations
581+describe one symbol rather than one being a translation of the other. Raw
582+pointers map too (`*mut T` to `ptr T`), which cleared all 38 raw-pointer
583+blockers in the survey.
584+
585+### `const` matters at the C level even though it does not at the Rust one
586+
587+Rust emits no C prototype; Nim emits a real one. So declaring `strlen` as
588+taking `*const u8` produces:
589+
590+```
591+error: conflicting types for 'strlen'; have 'NU(char *)'
592+note: previous declaration with type 'size_t(const char *)'
593+```
594+
595+which is the C compiler catching a declaration that does not match the symbol
596+— a loud failure, and a better outcome than Rust's silence. To make honest
597+declarations expressible, `*const T` maps to a generated const-qualified
598+alias:
599+
600+```nim
601+type RsConstPtrcchar* {.importc: "const char *", nodecl.} = distinct pointer
602+proc strlen*(s: RsConstPtrcchar): uint {.importc: "strlen", cdecl.}
603+```
604+
605+one per element type actually used. Nim's generic form (`importc: "const $1*"`)
606+was tried first and does not work in 2.2.4. A `*const T` whose C spelling we
607+do not know is rejected rather than declared without the `const`.
608+
609+`tests/cases/036-extern-c.rs` calls `abs`, `labs`, `strlen` and `atoi` through
610+this path, byte-identical to rustc.
611+
562 ## Proof of byte-identity for `base16ct`612 ## Proof of byte-identity for `base16ct`
563 613
564 [`PROOF.md`](PROOF.md) sets out what is actually established: exhaustive614 [`PROOF.md`](PROOF.md) sets out what is actually established: exhaustive
modified README.md +12 -0
@@ -91,6 +91,18 @@ same treatment. rustnim models log's *emitting* side: a transpiled library's
9191 `info!` calls work and, with no logger installed, do nothing, exactly as in
9292 Rust. Installing a logger is done from Nim with `rsLogSetLogger`.
9393
94+## C interop
95+
96+An `extern "C"` block lowers to Nim `importc` declarations — both are
97+statements about a symbol someone else defines, bound by the same ABI. Raw
98+pointers map, and `*const T` becomes a const-qualified C declaration, because
99+Nim emits a real prototype where Rust emits none and the C compiler will
100+reject a mismatch.
101+
102+That is the answer to `libc`, which is *not* transpiled: it is 129,594 lines
103+of which 54,544 are constants and 7,660 are declarations, against 121 function
104+bodies in the whole crate. Nim reaches those symbols natively.
105+
94106 ## Does it generalise?
95107
96108 `base16ct` is the crate this was built toward, so a second one was tried.
@@ -91,6 +91,18 @@ same treatment. rustnim models log's *emitting* side: a transpiled library's
91 `info!` calls work and, with no logger installed, do nothing, exactly as in91 `info!` calls work and, with no logger installed, do nothing, exactly as in
92 Rust. Installing a logger is done from Nim with `rsLogSetLogger`.92 Rust. Installing a logger is done from Nim with `rsLogSetLogger`.
93 93
94+## C interop
95+
96+An `extern "C"` block lowers to Nim `importc` declarations — both are
97+statements about a symbol someone else defines, bound by the same ABI. Raw
98+pointers map, and `*const T` becomes a const-qualified C declaration, because
99+Nim emits a real prototype where Rust emits none and the C compiler will
100+reject a mismatch.
101+
102+That is the answer to `libc`, which is *not* transpiled: it is 129,594 lines
103+of which 54,544 are constants and 7,660 are declarations, against 121 function
104+bodies in the whole crate. Nim reaches those symbols natively.
105+
94 ## Does it generalise?106 ## Does it generalise?
95 107
96 `base16ct` is the crate this was built toward, so a second one was tried.108 `base16ct` is the crate this was built toward, so a second one was tried.
modified src/lower.rs +153 -2
@@ -216,6 +216,10 @@ pub struct Lowerer {
216216 assoc: HashMap<(String, String), Nim>,
217217 /// `(type, name) -> (nim name, type)` for `const` items inside an `impl`.
218218 assoc_consts: HashMap<(String, String), (String, Nim)>,
219+ /// Symbols declared by an `extern "C"` block.
220+ foreign: std::collections::HashSet<String>,
221+ /// Const-qualified C pointer aliases already declared.
222+ const_ptrs: std::collections::HashSet<String>,
219223 /// Types declared by a `bitflags!` invocation.
220224 bitflags: std::collections::HashSet<String>,
221225 /// `(type, flag) -> nim const name`.
@@ -295,6 +299,8 @@ impl Lowerer {
295299 type_generics: HashMap::new(),
296300 assoc: HashMap::new(),
297301 assoc_consts: HashMap::new(),
302+ foreign: std::collections::HashSet::new(),
303+ const_ptrs: std::collections::HashSet::new(),
298304 bitflags: std::collections::HashSet::new(),
299305 flag_consts: HashMap::new(),
300306 use_map: HashMap::new(),
@@ -566,6 +572,17 @@ impl Lowerer {
566572 Item::Macro(m) if path_name(&m.mac.path) == "bitflags" => {
567573 self.collect_bitflags(&m.mac)?;
568574 }
575+ Item::ForeignMod(f) => {
576+ for it in &f.items {
577+ if let syn::ForeignItem::Fn(fi) = it {
578+ let (params, ret) = self.signature(&fi.sig)?;
579+ self.fns.insert(
580+ (self.cur_mod.clone(), fi.sig.ident.to_string()),
581+ Sig { params, ret, generics: Vec::new() },
582+ );
583+ }
584+ }
585+ }
569586 Item::Mod(m) if m.content.is_some() => {
570587 let items = m.content.as_ref().map(|(_, i)| i.clone()).unwrap_or_default();
571588 for i in &items {
@@ -1217,6 +1234,7 @@ impl Lowerer {
12171234 match item {
12181235 Item::Struct(_) | Item::Enum(_) | Item::Const(_) => self.item_inner(item),
12191236 Item::Macro(m) if path_name(&m.mac.path) == "bitflags" => self.item_inner(item),
1237+ Item::ForeignMod(_) => self.item_inner(item),
12201238 Item::Mod(m) if m.content.is_some() => {
12211239 let items = m.content.as_ref().map(|(_, i)| i.clone()).unwrap_or_default();
12221240 for i in &items {
@@ -1236,7 +1254,9 @@ impl Lowerer {
12361254 if matches!(item, Item::Struct(_) | Item::Enum(_) | Item::Const(_)) {
12371255 return Ok(());
12381256 }
1239- if matches!(item, Item::Macro(m) if path_name(&m.mac.path) == "bitflags") {
1257+ if matches!(item, Item::Macro(m) if path_name(&m.mac.path) == "bitflags")
1258+ || matches!(item, Item::ForeignMod(_))
1259+ {
12401260 return Ok(()); // emitted with the types
12411261 }
12421262 self.item_inner(item)
@@ -1329,6 +1349,7 @@ impl Lowerer {
13291349 // `use` and `extern crate` are resolution directives with no Nim
13301350 // analogue once everything is one module.
13311351 Item::Use(_) | Item::ExternCrate(_) => Ok(()),
1352+ Item::ForeignMod(f) => self.foreign_mod(f),
13321353 Item::Mod(m) if m.content.is_some() => {
13331354 // An inline `mod` is flattened; Nim has no nested modules in a
13341355 // single file.
@@ -1428,6 +1449,107 @@ impl Lowerer {
14281449 Ok(())
14291450 }
14301451
1452+ /// An `extern "C" { .. }` block: declarations of symbols someone else
1453+ /// defines. Nim's `importc` is the same statement, and both are bound by
1454+ /// the C ABI, so the two declarations describe one symbol rather than one
1455+ /// being a translation of the other.
1456+ fn foreign_mod(&mut self, f: &syn::ItemForeignMod) -> Result<(), String> {
1457+ let abi = f
1458+ .abi
1459+ .name
1460+ .as_ref()
1461+ .map(|s| s.value())
1462+ .unwrap_or_else(|| "C".into());
1463+ if abi != "C" {
1464+ return Err(format!(
1465+ "`extern \"{abi}\"` is not the C ABI; only that one has a Nim \
1466+ equivalent"
1467+ ));
1468+ }
1469+ // A const-qualified C pointer needs a type whose C spelling carries
1470+ // the `const`; Nim has no such type built in, so one is declared per
1471+ // element type actually used.
1472+ let mut needed: Vec<Nim> = Vec::new();
1473+ for it in &f.items {
1474+ if let syn::ForeignItem::Fn(fi) = it {
1475+ let (params, ret) = self.signature(&fi.sig)?;
1476+ for t in params.iter().chain(std::iter::once(&ret)) {
1477+ if let Nim::ConstPtr(inner) = t {
1478+ if !matches!(&**inner, Nim::Prim(p) if p == "void")
1479+ && !needed.contains(t)
1480+ {
1481+ needed.push(t.clone());
1482+ }
1483+ }
1484+ }
1485+ }
1486+ }
1487+ for t in &needed {
1488+ let Nim::ConstPtr(inner) = t else { continue };
1489+ let alias = ty::const_ptr_alias(inner);
1490+ if !self.const_ptrs.insert(alias.clone()) {
1491+ continue;
1492+ }
1493+ let c = ty::c_spelling(inner).ok_or_else(|| {
1494+ format!(
1495+ "`*const {}` has no C spelling we know, so a const-qualified \
1496+ declaration cannot be emitted for it",
1497+ inner.render()
1498+ )
1499+ })?;
1500+ self.line(&format!(
1501+ "type {}* {{.importc: \"const {} *\", nodecl.}} = distinct pointer",
1502+ alias, c
1503+ ));
1504+ }
1505+
1506+ for it in &f.items {
1507+ match it {
1508+ syn::ForeignItem::Fn(fi) => {
1509+ if fi.sig.variadic.is_some() {
1510+ return Err(format!(
1511+ "`{}` is variadic; Nim needs `varargs` with a fixed \
1512+ calling shape, which this does not give us",
1513+ fi.sig.ident
1514+ ));
1515+ }
1516+ let name = fi.sig.ident.to_string();
1517+ let head = self.head_of(&name, &fi.sig, None)?;
1518+ // `importc` names the C symbol, so the Nim name may differ
1519+ // from it without changing what is linked.
1520+ self.line(&format!(
1521+ "{} {{.importc: \"{}\", cdecl.}}",
1522+ head, name
1523+ ));
1524+ let (params, ret) = self.signature(&fi.sig)?;
1525+ self.fns.insert(
1526+ (self.cur_mod.clone(), name.clone()),
1527+ Sig { params, ret, generics: Vec::new() },
1528+ );
1529+ self.foreign.insert(name);
1530+ }
1531+ syn::ForeignItem::Static(st) => {
1532+ let t = self.map_ty(&st.ty)?;
1533+ let name = st.ident.to_string();
1534+ self.line(&format!(
1535+ "var {}* {{.importc: \"{}\".}}: {}",
1536+ ident(&name),
1537+ name,
1538+ t.render()
1539+ ));
1540+ self.bind(&name, t);
1541+ }
1542+ syn::ForeignItem::Type(_) => {
1543+ // An opaque C type: Nim spells it as a distinct object.
1544+ continue;
1545+ }
1546+ _ => return Err("unsupported item in an `extern` block".into()),
1547+ }
1548+ }
1549+ self.blank();
1550+ Ok(())
1551+ }
1552+
14311553 /// `const N: usize = 4;` inside an `impl`. Nim has no per-type constant
14321554 /// namespace, so it becomes a module-level const named for both.
14331555 fn assoc_const(&mut self, tyname: &str, c: &syn::ImplItemConst) -> Result<(), String> {
@@ -3714,6 +3836,17 @@ impl Lowerer {
37143836 (f, Nim::Prim(r)) if f.is_integer() && r == "Rune" => {
37153837 format!("Rune(int32({}))", v.code)
37163838 }
3839+ // Pointer-to-pointer, and integer-to-pointer, are reinterpretations
3840+ // in both languages.
3841+ (Nim::Ptr(_) | Nim::ConstPtr(_), Nim::Ptr(_) | Nim::ConstPtr(_)) => {
3842+ format!("cast[{}]({})", to.render(), v.code)
3843+ }
3844+ (f, Nim::Ptr(_) | Nim::ConstPtr(_)) if f.is_integer() => {
3845+ format!("cast[{}]({})", to.render(), v.code)
3846+ }
3847+ (Nim::Ptr(_) | Nim::ConstPtr(_), t) if t.is_integer() => {
3848+ format!("cast[{}]({})", t.render(), v.code)
3849+ }
37173850 (Nim::Prim(a), Nim::Prim(b)) if a == b => v.code.clone(),
37183851 (f, t) if matches!(f, Nim::Prim(p) if p.starts_with("float")) && t.is_integer() => {
37193852 // Rust saturates float->int casts; Nim rounds and range-errors.
@@ -4599,6 +4732,22 @@ impl Lowerer {
45994732 };
46004733 (format!("{}({}, {})", f, recv.code, arg.code), Some(out))
46014734 }
4735+ // `as_ptr` hands a C function the address of the first element,
4736+ // which is what Rust's does. An empty slice has no first element
4737+ // in either language, and reading through the pointer would be
4738+ // undefined in both.
4739+ "as_ptr" | "as_mut_ptr" => {
4740+ let elem = elem_of(&rt)
4741+ .ok_or("`as_ptr` needs a known element type")?;
4742+ (
4743+ format!(
4744+ "(if {r}.len == 0: nil else: cast[ptr {e}](addr {r}[0]))",
4745+ r = recv.code,
4746+ e = elem.render()
4747+ ),
4748+ Some(Nim::Ptr(Box::new(elem))),
4749+ )
4750+ }
46024751 "abs" => (format!("abs({})", recv.code), rt.clone()),
46034752 "min" => (format!("min({}, {})", recv.code, a0.unwrap_or_default()), rt.clone()),
46044753 "max" => (format!("max({}, {})", recv.code, a0.unwrap_or_default()), rt.clone()),
@@ -4985,6 +5134,9 @@ fn expressible(e: &Expr) -> bool {
49855134 },
49865135 }
49875136 }
5137+ // `unsafe { .. }` is transparent, so it is an expression exactly when
5138+ // its block is one.
5139+ Expr::Unsafe(u) => single_expr(&u.block).is_some_and(expressible),
49885140 Expr::Match(_) | Expr::Block(_) | Expr::Loop(_) | Expr::While(_) | Expr::ForLoop(_) => false,
49895141 _ => true,
49905142 }
@@ -5362,7 +5514,6 @@ fn item_kind(i: &Item) -> &'static str {
53625514 Item::Static(_) => "`static`",
53635515 Item::Macro(_) => "macro definition",
53645516 Item::Union(_) => "`union`",
5365- Item::ForeignMod(_) => "`extern` block",
53665517 _ => "item",
53675518 }
53685519 }
@@ -216,6 +216,10 @@ pub struct Lowerer {
216 assoc: HashMap<(String, String), Nim>,216 assoc: HashMap<(String, String), Nim>,
217 /// `(type, name) -> (nim name, type)` for `const` items inside an `impl`.217 /// `(type, name) -> (nim name, type)` for `const` items inside an `impl`.
218 assoc_consts: HashMap<(String, String), (String, Nim)>,218 assoc_consts: HashMap<(String, String), (String, Nim)>,
219+ /// Symbols declared by an `extern "C"` block.
220+ foreign: std::collections::HashSet<String>,
221+ /// Const-qualified C pointer aliases already declared.
222+ const_ptrs: std::collections::HashSet<String>,
219 /// Types declared by a `bitflags!` invocation.223 /// Types declared by a `bitflags!` invocation.
220 bitflags: std::collections::HashSet<String>,224 bitflags: std::collections::HashSet<String>,
221 /// `(type, flag) -> nim const name`.225 /// `(type, flag) -> nim const name`.
@@ -295,6 +299,8 @@ impl Lowerer {
295 type_generics: HashMap::new(),299 type_generics: HashMap::new(),
296 assoc: HashMap::new(),300 assoc: HashMap::new(),
297 assoc_consts: HashMap::new(),301 assoc_consts: HashMap::new(),
302+ foreign: std::collections::HashSet::new(),
303+ const_ptrs: std::collections::HashSet::new(),
298 bitflags: std::collections::HashSet::new(),304 bitflags: std::collections::HashSet::new(),
299 flag_consts: HashMap::new(),305 flag_consts: HashMap::new(),
300 use_map: HashMap::new(),306 use_map: HashMap::new(),
@@ -566,6 +572,17 @@ impl Lowerer {
566 Item::Macro(m) if path_name(&m.mac.path) == "bitflags" => {572 Item::Macro(m) if path_name(&m.mac.path) == "bitflags" => {
567 self.collect_bitflags(&m.mac)?;573 self.collect_bitflags(&m.mac)?;
568 }574 }
575+ Item::ForeignMod(f) => {
576+ for it in &f.items {
577+ if let syn::ForeignItem::Fn(fi) = it {
578+ let (params, ret) = self.signature(&fi.sig)?;
579+ self.fns.insert(
580+ (self.cur_mod.clone(), fi.sig.ident.to_string()),
581+ Sig { params, ret, generics: Vec::new() },
582+ );
583+ }
584+ }
585+ }
569 Item::Mod(m) if m.content.is_some() => {586 Item::Mod(m) if m.content.is_some() => {
570 let items = m.content.as_ref().map(|(_, i)| i.clone()).unwrap_or_default();587 let items = m.content.as_ref().map(|(_, i)| i.clone()).unwrap_or_default();
571 for i in &items {588 for i in &items {
@@ -1217,6 +1234,7 @@ impl Lowerer {
1217 match item {1234 match item {
1218 Item::Struct(_) | Item::Enum(_) | Item::Const(_) => self.item_inner(item),1235 Item::Struct(_) | Item::Enum(_) | Item::Const(_) => self.item_inner(item),
1219 Item::Macro(m) if path_name(&m.mac.path) == "bitflags" => self.item_inner(item),1236 Item::Macro(m) if path_name(&m.mac.path) == "bitflags" => self.item_inner(item),
1237+ Item::ForeignMod(_) => self.item_inner(item),
1220 Item::Mod(m) if m.content.is_some() => {1238 Item::Mod(m) if m.content.is_some() => {
1221 let items = m.content.as_ref().map(|(_, i)| i.clone()).unwrap_or_default();1239 let items = m.content.as_ref().map(|(_, i)| i.clone()).unwrap_or_default();
1222 for i in &items {1240 for i in &items {
@@ -1236,7 +1254,9 @@ impl Lowerer {
1236 if matches!(item, Item::Struct(_) | Item::Enum(_) | Item::Const(_)) {1254 if matches!(item, Item::Struct(_) | Item::Enum(_) | Item::Const(_)) {
1237 return Ok(());1255 return Ok(());
1238 }1256 }
1239- if matches!(item, Item::Macro(m) if path_name(&m.mac.path) == "bitflags") {1257+ if matches!(item, Item::Macro(m) if path_name(&m.mac.path) == "bitflags")
1258+ || matches!(item, Item::ForeignMod(_))
1259+ {
1240 return Ok(()); // emitted with the types1260 return Ok(()); // emitted with the types
1241 }1261 }
1242 self.item_inner(item)1262 self.item_inner(item)
@@ -1329,6 +1349,7 @@ impl Lowerer {
1329 // `use` and `extern crate` are resolution directives with no Nim1349 // `use` and `extern crate` are resolution directives with no Nim
1330 // analogue once everything is one module.1350 // analogue once everything is one module.
1331 Item::Use(_) | Item::ExternCrate(_) => Ok(()),1351 Item::Use(_) | Item::ExternCrate(_) => Ok(()),
1352+ Item::ForeignMod(f) => self.foreign_mod(f),
1332 Item::Mod(m) if m.content.is_some() => {1353 Item::Mod(m) if m.content.is_some() => {
1333 // An inline `mod` is flattened; Nim has no nested modules in a1354 // An inline `mod` is flattened; Nim has no nested modules in a
1334 // single file.1355 // single file.
@@ -1428,6 +1449,107 @@ impl Lowerer {
1428 Ok(())1449 Ok(())
1429 }1450 }
1430 1451
1452+ /// An `extern "C" { .. }` block: declarations of symbols someone else
1453+ /// defines. Nim's `importc` is the same statement, and both are bound by
1454+ /// the C ABI, so the two declarations describe one symbol rather than one
1455+ /// being a translation of the other.
1456+ fn foreign_mod(&mut self, f: &syn::ItemForeignMod) -> Result<(), String> {
1457+ let abi = f
1458+ .abi
1459+ .name
1460+ .as_ref()
1461+ .map(|s| s.value())
1462+ .unwrap_or_else(|| "C".into());
1463+ if abi != "C" {
1464+ return Err(format!(
1465+ "`extern \"{abi}\"` is not the C ABI; only that one has a Nim \
1466+ equivalent"
1467+ ));
1468+ }
1469+ // A const-qualified C pointer needs a type whose C spelling carries
1470+ // the `const`; Nim has no such type built in, so one is declared per
1471+ // element type actually used.
1472+ let mut needed: Vec<Nim> = Vec::new();
1473+ for it in &f.items {
1474+ if let syn::ForeignItem::Fn(fi) = it {
1475+ let (params, ret) = self.signature(&fi.sig)?;
1476+ for t in params.iter().chain(std::iter::once(&ret)) {
1477+ if let Nim::ConstPtr(inner) = t {
1478+ if !matches!(&**inner, Nim::Prim(p) if p == "void")
1479+ && !needed.contains(t)
1480+ {
1481+ needed.push(t.clone());
1482+ }
1483+ }
1484+ }
1485+ }
1486+ }
1487+ for t in &needed {
1488+ let Nim::ConstPtr(inner) = t else { continue };
1489+ let alias = ty::const_ptr_alias(inner);
1490+ if !self.const_ptrs.insert(alias.clone()) {
1491+ continue;
1492+ }
1493+ let c = ty::c_spelling(inner).ok_or_else(|| {
1494+ format!(
1495+ "`*const {}` has no C spelling we know, so a const-qualified \
1496+ declaration cannot be emitted for it",
1497+ inner.render()
1498+ )
1499+ })?;
1500+ self.line(&format!(
1501+ "type {}* {{.importc: \"const {} *\", nodecl.}} = distinct pointer",
1502+ alias, c
1503+ ));
1504+ }
1505+
1506+ for it in &f.items {
1507+ match it {
1508+ syn::ForeignItem::Fn(fi) => {
1509+ if fi.sig.variadic.is_some() {
1510+ return Err(format!(
1511+ "`{}` is variadic; Nim needs `varargs` with a fixed \
1512+ calling shape, which this does not give us",
1513+ fi.sig.ident
1514+ ));
1515+ }
1516+ let name = fi.sig.ident.to_string();
1517+ let head = self.head_of(&name, &fi.sig, None)?;
1518+ // `importc` names the C symbol, so the Nim name may differ
1519+ // from it without changing what is linked.
1520+ self.line(&format!(
1521+ "{} {{.importc: \"{}\", cdecl.}}",
1522+ head, name
1523+ ));
1524+ let (params, ret) = self.signature(&fi.sig)?;
1525+ self.fns.insert(
1526+ (self.cur_mod.clone(), name.clone()),
1527+ Sig { params, ret, generics: Vec::new() },
1528+ );
1529+ self.foreign.insert(name);
1530+ }
1531+ syn::ForeignItem::Static(st) => {
1532+ let t = self.map_ty(&st.ty)?;
1533+ let name = st.ident.to_string();
1534+ self.line(&format!(
1535+ "var {}* {{.importc: \"{}\".}}: {}",
1536+ ident(&name),
1537+ name,
1538+ t.render()
1539+ ));
1540+ self.bind(&name, t);
1541+ }
1542+ syn::ForeignItem::Type(_) => {
1543+ // An opaque C type: Nim spells it as a distinct object.
1544+ continue;
1545+ }
1546+ _ => return Err("unsupported item in an `extern` block".into()),
1547+ }
1548+ }
1549+ self.blank();
1550+ Ok(())
1551+ }
1552+
1431 /// `const N: usize = 4;` inside an `impl`. Nim has no per-type constant1553 /// `const N: usize = 4;` inside an `impl`. Nim has no per-type constant
1432 /// namespace, so it becomes a module-level const named for both.1554 /// namespace, so it becomes a module-level const named for both.
1433 fn assoc_const(&mut self, tyname: &str, c: &syn::ImplItemConst) -> Result<(), String> {1555 fn assoc_const(&mut self, tyname: &str, c: &syn::ImplItemConst) -> Result<(), String> {
@@ -3714,6 +3836,17 @@ impl Lowerer {
3714 (f, Nim::Prim(r)) if f.is_integer() && r == "Rune" => {3836 (f, Nim::Prim(r)) if f.is_integer() && r == "Rune" => {
3715 format!("Rune(int32({}))", v.code)3837 format!("Rune(int32({}))", v.code)
3716 }3838 }
3839+ // Pointer-to-pointer, and integer-to-pointer, are reinterpretations
3840+ // in both languages.
3841+ (Nim::Ptr(_) | Nim::ConstPtr(_), Nim::Ptr(_) | Nim::ConstPtr(_)) => {
3842+ format!("cast[{}]({})", to.render(), v.code)
3843+ }
3844+ (f, Nim::Ptr(_) | Nim::ConstPtr(_)) if f.is_integer() => {
3845+ format!("cast[{}]({})", to.render(), v.code)
3846+ }
3847+ (Nim::Ptr(_) | Nim::ConstPtr(_), t) if t.is_integer() => {
3848+ format!("cast[{}]({})", t.render(), v.code)
3849+ }
3717 (Nim::Prim(a), Nim::Prim(b)) if a == b => v.code.clone(),3850 (Nim::Prim(a), Nim::Prim(b)) if a == b => v.code.clone(),
3718 (f, t) if matches!(f, Nim::Prim(p) if p.starts_with("float")) && t.is_integer() => {3851 (f, t) if matches!(f, Nim::Prim(p) if p.starts_with("float")) && t.is_integer() => {
3719 // Rust saturates float->int casts; Nim rounds and range-errors.3852 // Rust saturates float->int casts; Nim rounds and range-errors.
@@ -4599,6 +4732,22 @@ impl Lowerer {
4599 };4732 };
4600 (format!("{}({}, {})", f, recv.code, arg.code), Some(out))4733 (format!("{}({}, {})", f, recv.code, arg.code), Some(out))
4601 }4734 }
4735+ // `as_ptr` hands a C function the address of the first element,
4736+ // which is what Rust's does. An empty slice has no first element
4737+ // in either language, and reading through the pointer would be
4738+ // undefined in both.
4739+ "as_ptr" | "as_mut_ptr" => {
4740+ let elem = elem_of(&rt)
4741+ .ok_or("`as_ptr` needs a known element type")?;
4742+ (
4743+ format!(
4744+ "(if {r}.len == 0: nil else: cast[ptr {e}](addr {r}[0]))",
4745+ r = recv.code,
4746+ e = elem.render()
4747+ ),
4748+ Some(Nim::Ptr(Box::new(elem))),
4749+ )
4750+ }
4602 "abs" => (format!("abs({})", recv.code), rt.clone()),4751 "abs" => (format!("abs({})", recv.code), rt.clone()),
4603 "min" => (format!("min({}, {})", recv.code, a0.unwrap_or_default()), rt.clone()),4752 "min" => (format!("min({}, {})", recv.code, a0.unwrap_or_default()), rt.clone()),
4604 "max" => (format!("max({}, {})", recv.code, a0.unwrap_or_default()), rt.clone()),4753 "max" => (format!("max({}, {})", recv.code, a0.unwrap_or_default()), rt.clone()),
@@ -4985,6 +5134,9 @@ fn expressible(e: &Expr) -> bool {
4985 },5134 },
4986 }5135 }
4987 }5136 }
5137+ // `unsafe { .. }` is transparent, so it is an expression exactly when
5138+ // its block is one.
5139+ Expr::Unsafe(u) => single_expr(&u.block).is_some_and(expressible),
4988 Expr::Match(_) | Expr::Block(_) | Expr::Loop(_) | Expr::While(_) | Expr::ForLoop(_) => false,5140 Expr::Match(_) | Expr::Block(_) | Expr::Loop(_) | Expr::While(_) | Expr::ForLoop(_) => false,
4989 _ => true,5141 _ => true,
4990 }5142 }
@@ -5362,7 +5514,6 @@ fn item_kind(i: &Item) -> &'static str {
5362 Item::Static(_) => "`static`",5514 Item::Static(_) => "`static`",
5363 Item::Macro(_) => "macro definition",5515 Item::Macro(_) => "macro definition",
5364 Item::Union(_) => "`union`",5516 Item::Union(_) => "`union`",
5365- Item::ForeignMod(_) => "`extern` block",
5366 _ => "item",5517 _ => "item",
5367 }5518 }
5368 }5519 }
modified src/ty.rs +87 -2
@@ -16,6 +16,12 @@ pub enum Nim {
1616 Tuple(Vec<Nim>),
1717 Named(String, Vec<Nim>),
1818 Var(Box<Nim>),
19+ /// `*mut T`. Nim's `ptr` is the same thing: an unmanaged address.
20+ Ptr(Box<Nim>),
21+ /// `*const T`. The const matters at the C level even though it does not at
22+ /// the Rust one: Nim emits a real prototype where Rust emits none, and a
23+ /// `char *` declaration against C's `const char *` is a compile error.
24+ ConstPtr(Box<Nim>),
1925 /// `impl Fn(A) -> B` / `fn(A) -> B`. Left at Nim's default calling
2026 /// convention (`closure`), which accepts both a plain top-level proc and
2127 /// a closure that captures -- and Rust's `impl Fn` accepts both too.
@@ -40,6 +46,14 @@ impl Nim {
4046 format!("{}[{}]", n, inner.join(", "))
4147 }
4248 Nim::Var(t) => format!("var {}", t.render()),
49+ Nim::Ptr(t) => match &**t {
50+ Nim::Prim(p) if p == "void" => "pointer".into(),
51+ inner => format!("ptr {}", inner.render()),
52+ },
53+ Nim::ConstPtr(t) => match &**t {
54+ Nim::Prim(p) if p == "void" => "pointer".into(),
55+ inner => const_ptr_alias(inner),
56+ },
4357 Nim::Proc(args, ret) => {
4458 // Nim's proc types name their parameters even when the name is
4559 // never used.
@@ -87,6 +101,33 @@ impl Nim {
87101 }
88102 }
89103
104+/// C types, as `libc` spells them. Nim has the same set under its own names,
105+/// and both are defined by the platform's C compiler, so these are equal by
106+/// construction rather than by assumption.
107+pub fn c_type(name: &str) -> Option<&'static str> {
108+ Some(match name {
109+ "c_char" => "cchar",
110+ "c_schar" => "cschar",
111+ "c_uchar" => "cuchar",
112+ "c_short" => "cshort",
113+ "c_ushort" => "cushort",
114+ "c_int" => "cint",
115+ "c_uint" => "cuint",
116+ "c_long" => "clong",
117+ "c_ulong" => "culong",
118+ "c_longlong" => "clonglong",
119+ "c_ulonglong" => "culonglong",
120+ "c_float" => "cfloat",
121+ "c_double" => "cdouble",
122+ "size_t" => "csize_t",
123+ "ssize_t" => "int",
124+ "intptr_t" => "int",
125+ "uintptr_t" => "uint",
126+ "c_void" => "void",
127+ _ => return None,
128+ })
129+}
130+
90131 pub fn prim(name: &str) -> Option<Nim> {
91132 let mapped = match name {
92133 "i8" => "int8",
@@ -104,7 +145,10 @@ pub fn prim(name: &str) -> Option<Nim> {
104145 "bool" => "bool",
105146 "char" => "Rune",
106147 "str" | "String" => "string",
107- _ => return None,
148+ other => match c_type(other) {
149+ Some(c) => c,
150+ None => return None,
151+ },
108152 };
109153 Some(Nim::Prim(mapped.into()))
110154 }
@@ -117,6 +161,40 @@ pub fn rejected(name: &str) -> Option<&'static str> {
117161 }
118162 }
119163
164+/// The Nim type name for a const-qualified C pointer to `t`.
165+pub fn const_ptr_alias(t: &Nim) -> String {
166+ format!("RsConstPtr{}", t.render().replace(' ', ""))
167+}
168+
169+/// The C spelling of a Nim type, for a const-qualified pointer declaration.
170+pub fn c_spelling(t: &Nim) -> Option<&'static str> {
171+ let Nim::Prim(p) = t else { return None };
172+ Some(match p.as_str() {
173+ "cchar" => "char",
174+ "cschar" => "signed char",
175+ "cuchar" => "unsigned char",
176+ "cshort" => "short",
177+ "cushort" => "unsigned short",
178+ "cint" => "int",
179+ "cuint" => "unsigned int",
180+ "clong" => "long",
181+ "culong" => "unsigned long",
182+ "clonglong" => "long long",
183+ "culonglong" => "unsigned long long",
184+ "cfloat" => "float",
185+ "cdouble" => "double",
186+ "uint8" => "unsigned char",
187+ "int8" => "signed char",
188+ "uint16" => "unsigned short",
189+ "int16" => "short",
190+ "uint32" => "unsigned int",
191+ "int32" => "int",
192+ "uint64" => "unsigned long long",
193+ "int64" => "long long",
194+ _ => return None,
195+ })
196+}
197+
120198 fn ret_ty(r: &syn::ReturnType) -> Result<Nim, String> {
121199 match r {
122200 syn::ReturnType::Default => Ok(Nim::Unit),
@@ -215,6 +293,14 @@ pub fn map(t: &Type) -> Result<Nim, String> {
215293 Ok(inner)
216294 }
217295 }
296+ Type::Ptr(p) => {
297+ let inner = Box::new(map(&p.elem)?);
298+ Ok(if matches!(p.mutability, syn::PointerMutability::Mut(_)) {
299+ Nim::Ptr(inner)
300+ } else {
301+ Nim::ConstPtr(inner)
302+ })
303+ }
218304 Type::Slice(s) => Ok(Nim::OpenArray(Box::new(map(&s.elem)?))),
219305 Type::Array(a) => {
220306 let len = match &a.len {
@@ -309,7 +395,6 @@ pub fn map(t: &Type) -> Result<Nim, String> {
309395
310396 fn discriminant(t: &Type) -> &'static str {
311397 match t {
312- Type::Ptr(_) => "raw pointer",
313398 Type::TraitObject(_) => "trait object",
314399 Type::Never(_) => "never",
315400 Type::Macro(_) => "macro",
@@ -16,6 +16,12 @@ pub enum Nim {
16 Tuple(Vec<Nim>),16 Tuple(Vec<Nim>),
17 Named(String, Vec<Nim>),17 Named(String, Vec<Nim>),
18 Var(Box<Nim>),18 Var(Box<Nim>),
19+ /// `*mut T`. Nim's `ptr` is the same thing: an unmanaged address.
20+ Ptr(Box<Nim>),
21+ /// `*const T`. The const matters at the C level even though it does not at
22+ /// the Rust one: Nim emits a real prototype where Rust emits none, and a
23+ /// `char *` declaration against C's `const char *` is a compile error.
24+ ConstPtr(Box<Nim>),
19 /// `impl Fn(A) -> B` / `fn(A) -> B`. Left at Nim's default calling25 /// `impl Fn(A) -> B` / `fn(A) -> B`. Left at Nim's default calling
20 /// convention (`closure`), which accepts both a plain top-level proc and26 /// convention (`closure`), which accepts both a plain top-level proc and
21 /// a closure that captures -- and Rust's `impl Fn` accepts both too.27 /// a closure that captures -- and Rust's `impl Fn` accepts both too.
@@ -40,6 +46,14 @@ impl Nim {
40 format!("{}[{}]", n, inner.join(", "))46 format!("{}[{}]", n, inner.join(", "))
41 }47 }
42 Nim::Var(t) => format!("var {}", t.render()),48 Nim::Var(t) => format!("var {}", t.render()),
49+ Nim::Ptr(t) => match &**t {
50+ Nim::Prim(p) if p == "void" => "pointer".into(),
51+ inner => format!("ptr {}", inner.render()),
52+ },
53+ Nim::ConstPtr(t) => match &**t {
54+ Nim::Prim(p) if p == "void" => "pointer".into(),
55+ inner => const_ptr_alias(inner),
56+ },
43 Nim::Proc(args, ret) => {57 Nim::Proc(args, ret) => {
44 // Nim's proc types name their parameters even when the name is58 // Nim's proc types name their parameters even when the name is
45 // never used.59 // never used.
@@ -87,6 +101,33 @@ impl Nim {
87 }101 }
88 }102 }
89 103
104+/// C types, as `libc` spells them. Nim has the same set under its own names,
105+/// and both are defined by the platform's C compiler, so these are equal by
106+/// construction rather than by assumption.
107+pub fn c_type(name: &str) -> Option<&'static str> {
108+ Some(match name {
109+ "c_char" => "cchar",
110+ "c_schar" => "cschar",
111+ "c_uchar" => "cuchar",
112+ "c_short" => "cshort",
113+ "c_ushort" => "cushort",
114+ "c_int" => "cint",
115+ "c_uint" => "cuint",
116+ "c_long" => "clong",
117+ "c_ulong" => "culong",
118+ "c_longlong" => "clonglong",
119+ "c_ulonglong" => "culonglong",
120+ "c_float" => "cfloat",
121+ "c_double" => "cdouble",
122+ "size_t" => "csize_t",
123+ "ssize_t" => "int",
124+ "intptr_t" => "int",
125+ "uintptr_t" => "uint",
126+ "c_void" => "void",
127+ _ => return None,
128+ })
129+}
130+
90 pub fn prim(name: &str) -> Option<Nim> {131 pub fn prim(name: &str) -> Option<Nim> {
91 let mapped = match name {132 let mapped = match name {
92 "i8" => "int8",133 "i8" => "int8",
@@ -104,7 +145,10 @@ pub fn prim(name: &str) -> Option<Nim> {
104 "bool" => "bool",145 "bool" => "bool",
105 "char" => "Rune",146 "char" => "Rune",
106 "str" | "String" => "string",147 "str" | "String" => "string",
107- _ => return None,148+ other => match c_type(other) {
149+ Some(c) => c,
150+ None => return None,
151+ },
108 };152 };
109 Some(Nim::Prim(mapped.into()))153 Some(Nim::Prim(mapped.into()))
110 }154 }
@@ -117,6 +161,40 @@ pub fn rejected(name: &str) -> Option<&'static str> {
117 }161 }
118 }162 }
119 163
164+/// The Nim type name for a const-qualified C pointer to `t`.
165+pub fn const_ptr_alias(t: &Nim) -> String {
166+ format!("RsConstPtr{}", t.render().replace(' ', ""))
167+}
168+
169+/// The C spelling of a Nim type, for a const-qualified pointer declaration.
170+pub fn c_spelling(t: &Nim) -> Option<&'static str> {
171+ let Nim::Prim(p) = t else { return None };
172+ Some(match p.as_str() {
173+ "cchar" => "char",
174+ "cschar" => "signed char",
175+ "cuchar" => "unsigned char",
176+ "cshort" => "short",
177+ "cushort" => "unsigned short",
178+ "cint" => "int",
179+ "cuint" => "unsigned int",
180+ "clong" => "long",
181+ "culong" => "unsigned long",
182+ "clonglong" => "long long",
183+ "culonglong" => "unsigned long long",
184+ "cfloat" => "float",
185+ "cdouble" => "double",
186+ "uint8" => "unsigned char",
187+ "int8" => "signed char",
188+ "uint16" => "unsigned short",
189+ "int16" => "short",
190+ "uint32" => "unsigned int",
191+ "int32" => "int",
192+ "uint64" => "unsigned long long",
193+ "int64" => "long long",
194+ _ => return None,
195+ })
196+}
197+
120 fn ret_ty(r: &syn::ReturnType) -> Result<Nim, String> {198 fn ret_ty(r: &syn::ReturnType) -> Result<Nim, String> {
121 match r {199 match r {
122 syn::ReturnType::Default => Ok(Nim::Unit),200 syn::ReturnType::Default => Ok(Nim::Unit),
@@ -215,6 +293,14 @@ pub fn map(t: &Type) -> Result<Nim, String> {
215 Ok(inner)293 Ok(inner)
216 }294 }
217 }295 }
296+ Type::Ptr(p) => {
297+ let inner = Box::new(map(&p.elem)?);
298+ Ok(if matches!(p.mutability, syn::PointerMutability::Mut(_)) {
299+ Nim::Ptr(inner)
300+ } else {
301+ Nim::ConstPtr(inner)
302+ })
303+ }
218 Type::Slice(s) => Ok(Nim::OpenArray(Box::new(map(&s.elem)?))),304 Type::Slice(s) => Ok(Nim::OpenArray(Box::new(map(&s.elem)?))),
219 Type::Array(a) => {305 Type::Array(a) => {
220 let len = match &a.len {306 let len = match &a.len {
@@ -309,7 +395,6 @@ pub fn map(t: &Type) -> Result<Nim, String> {
309 395
310 fn discriminant(t: &Type) -> &'static str {396 fn discriminant(t: &Type) -> &'static str {
311 match t {397 match t {
312- Type::Ptr(_) => "raw pointer",
313 Type::TraitObject(_) => "trait object",398 Type::TraitObject(_) => "trait object",
314 Type::Never(_) => "never",399 Type::Never(_) => "never",
315 Type::Macro(_) => "macro",400 Type::Macro(_) => "macro",
added tests/cases/036-extern-c.rs +40 -0
new file mode 100644
@@ -0,0 +1,40 @@
1+// An `extern "C"` block declares symbols someone else defines. Nim's
2+// `importc` is the same statement, and both are bound by the C ABI, so the
3+// two declarations describe one symbol rather than one being a translation of
4+// the other. This is what a crate that would otherwise reach for `libc` needs.
5+//
6+// `libc` itself is not transpiled and should not be: it is 129,594 lines of
7+// which 54,544 are constants and 7,660 are declarations like these, against
8+// 121 actual function bodies in the whole crate. Nim reaches the same symbols
9+// natively, so there is nothing to translate.
10+//
11+// The C types must be declared honestly. Nim emits a real C prototype where
12+// Rust does not, so declaring `strlen` as taking `*const u8` is caught by the
13+// C compiler rather than silently linking against a different signature.
14+
15+#[allow(non_camel_case_types)]
16+type c_char = i8;
17+#[allow(non_camel_case_types)]
18+type size_t = usize;
19+
20+extern "C" {
21+ fn abs(x: i32) -> i32;
22+ fn strlen(s: *const c_char) -> size_t;
23+ fn atoi(s: *const c_char) -> i32;
24+ fn labs(x: i64) -> i64;
25+}
26+
27+fn cstr(b: &[u8]) -> *const c_char {
28+ b.as_ptr() as *const c_char
29+}
30+
31+fn main() {
32+ unsafe {
33+ println!("{} {} {}", abs(-5), abs(0), abs(7));
34+ println!("{}", labs(-9000000000));
35+ println!("{}", strlen(cstr(b"hello\0")));
36+ println!("{}", strlen(cstr(b"\0")));
37+ println!("{}", atoi(cstr(b"-1234\0")));
38+ println!("{}", atoi(cstr(b"42abc\0")));
39+ }
40+}
new file mode 100644
@@ -0,0 +1,40 @@
1+// An `extern "C"` block declares symbols someone else defines. Nim's
2+// `importc` is the same statement, and both are bound by the C ABI, so the
3+// two declarations describe one symbol rather than one being a translation of
4+// the other. This is what a crate that would otherwise reach for `libc` needs.
5+//
6+// `libc` itself is not transpiled and should not be: it is 129,594 lines of
7+// which 54,544 are constants and 7,660 are declarations like these, against
8+// 121 actual function bodies in the whole crate. Nim reaches the same symbols
9+// natively, so there is nothing to translate.
10+//
11+// The C types must be declared honestly. Nim emits a real C prototype where
12+// Rust does not, so declaring `strlen` as taking `*const u8` is caught by the
13+// C compiler rather than silently linking against a different signature.
14+
15+#[allow(non_camel_case_types)]
16+type c_char = i8;
17+#[allow(non_camel_case_types)]
18+type size_t = usize;
19+
20+extern "C" {
21+ fn abs(x: i32) -> i32;
22+ fn strlen(s: *const c_char) -> size_t;
23+ fn atoi(s: *const c_char) -> i32;
24+ fn labs(x: i64) -> i64;
25+}
26+
27+fn cstr(b: &[u8]) -> *const c_char {
28+ b.as_ptr() as *const c_char
29+}
30+
31+fn main() {
32+ unsafe {
33+ println!("{} {} {}", abs(-5), abs(0), abs(7));
34+ println!("{}", labs(-9000000000));
35+ println!("{}", strlen(cstr(b"hello\0")));
36+ println!("{}", strlen(cstr(b"\0")));
37+ println!("{}", atoi(cstr(b"-1234\0")));
38+ println!("{}", atoi(cstr(b"42abc\0")));
39+ }
40+}