c_ffi/
lib.rs

1//!Collection of utilities to work with C stuff
2#![warn(missing_docs)]
3#![cfg_attr(feature = "cargo-clippy", allow(clippy::style))]
4#![no_std]
5
6mod sys;
7pub use sys::*;
8
9#[cfg(feature = "libc")]
10pub mod locale;
11#[cfg(all(feature = "libc", feature = "memory"))]
12pub mod memory;
13#[cfg(all(feature = "libc", feature = "memory"))]
14pub mod env;
15#[cfg(feature = "libc")]
16pub mod process;
17pub mod args;
18pub use args::Args;
19
20#[cfg(feature = "libc")]
21#[allow(non_camel_case_types)]
22///Alias to C's `int` type
23///
24///With `libc` enabled, alias to `libc::c_int`
25pub type int = libc::c_int;
26#[cfg(not(feature = "libc"))]
27#[allow(non_camel_case_types)]
28///Alias to C's `int` type
29pub type int = i32;
30
31///Converts C string to Rust's, verifying it is UTF-8
32///
33///It is UB to pass non-C string as it requires \0
34pub unsafe fn c_str_to_rust(ptr: *const u8) -> Result<&'static str, core::str::Utf8Error> {
35    let len = strlen(ptr as *const i8);
36    let parts = core::slice::from_raw_parts(ptr, len);
37    core::str::from_utf8(parts)
38}
39
40///Converts C string to Rust's one assuming it is UTF-8
41///
42///It is UB to pass non-C string as it requires \0
43pub unsafe fn c_str_to_rust_unchecked(ptr: *const u8) -> &'static str {
44    let len = strlen(ptr as *const i8);
45    let parts = core::slice::from_raw_parts(ptr, len);
46    core::str::from_utf8_unchecked(parts)
47}
48
49#[cfg(any(windows, unix, target_env = "wasi", target_os = "wasi", target_os = "vxworks", target_os = "fuchsia"))]
50#[doc(hidden)]
51#[cold]
52#[inline(never)]
53pub unsafe fn invalid_cli_args_error() -> int {
54    #[cfg_attr(all(windows, target_env="msvc"), link(name="legacy_stdio_definitions", kind="dylib"))]
55    extern "C" {
56        pub fn printf(format: *const i8, ...) -> int;
57    }
58
59    printf(c_lit!("Unable to use non-utf8 arguments\n").as_ptr() as _);
60    1
61}
62
63#[cfg(not(any(windows, unix, target_env = "wasi", target_os = "wasi", target_os = "vxworks", target_os = "fuchsia")))]
64#[doc(hidden)]
65#[cold]
66#[inline(never)]
67pub unsafe fn invalid_cli_args_error() -> int {
68    panic!("Unable to use non-utf8 arguments");
69}
70
71///Creates C compatible string that ends with null character
72#[macro_export]
73macro_rules! c_lit {
74    ($e:expr) => {
75        core::concat!($e, "\0")
76    };
77    ($($e:tt)+) => {
78        core::concat!($($e)+, "\0")
79    };
80}
81
82///Declares main function with C like signature, passing CLI arugments via `Args`
83///
84///## Arguments
85///
86///- `runner` - Function name to accepts `Args` instance
87///- `int` - Integer type used for `argc` and return value. In C it is `int` and therefore by default macro uses `$crate::int`.
88///
89///## Return value
90///
91///C++ `main` expects integer return value, therefore it automatically converts to `int` type
92///
93///## Panic
94///
95///In case of supplied command line arguments contain non-UTF8 string, then terminates.
96///
97///If `printf` is avalable then it prints to `stdout` and exits with return code 1
98///Otherwise it panics.
99///
100///## Usage
101///
102/// Default `int` type`
103///
104///```rust
105///#![no_main]
106///
107///fn my_main(args: c_ffi::Args) -> bool {
108///    match args.into_iter().count() {
109///        0 => true,
110///        _ => false,
111///    }
112///}
113///
114///c_ffi::c_main!(my_main);
115/////In case you'd like to specify return type yourself
116/////c_ffi::c_main!(my_main -> i32);
117#[macro_export]
118macro_rules! c_main {
119    ($runner:ident) => {
120        use $crate::int;
121        $crate::c_main!($runner -> int);
122    };
123    ($runner:ident -> $int:ident) => {
124        #[no_mangle]
125        pub unsafe extern fn main(argc: $int, argv: *const *const u8) -> $int {
126            match $crate::Args::new(argc as isize, argv) {
127                Ok(args) => $runner(args).into(),
128                Err(_) => $crate::invalid_cli_args_error(),
129            }
130        }
131    }
132}