Initial commit

Add a minimal no-core Rust executable built by invoking rustc directly from CMake.

The project avoids Cargo and Rust core, alloc, and std, writes to stdout
through platform APIs, and supports optimized Ninja release builds.

Co-Authored-By: luna (openrouter/openai/gpt-5.6-luna): project setup and build
This commit is contained in:
2026-09-05 04:30:43 +02:00
commit 54051d4068
4 changed files with 270 additions and 0 deletions
+4
View File
@@ -0,0 +1,4 @@
.cache
/target
.env
/build
+119
View File
@@ -0,0 +1,119 @@
# AGENTS.md
## Project Overview
This is a minimal Rust experiment that builds one executable without Cargo
and without linking Rust `core`, `alloc`, or `std`. CMake invokes `rustc`
directly. The executable writes `Hello` to stdout and exits.
The project currently contains:
- `main.rs`: the `#![no_core]` Rust program
- `CMakeLists.txt`: the direct `rustc` custom command
- `.gitignore`: ignores generated build output
## Non-Negotiable No-Core Design
- Keep `#![no_core]` in `main.rs`.
- Do not replace `#![no_core]` with `#![no_std]`; `no_std` still links
Rust `core`.
- Do not add Cargo files or invoke Cargo.
- Do not import or link `core`, `alloc`, or `std`.
- Keep the required language-item and marker-trait definitions local to
`main.rs` unless the user explicitly asks for a different design.
- Use primitive Rust types and explicit platform FFI instead of library
helpers.
- Keep unsafe code inside small safe wrappers. The public call site for
stdout should remain a normal string call such as:
```rust
write_stdout("Hello\\n");
```
## Output Implementation
- Unix builds call the libc `write` function with file descriptor `1`.
- Windows builds call `GetStdHandle`, `WriteFile`, and `ExitProcess`
directly through `kernel32`.
- The Windows build must not link the C runtime. This keeps the executable
small and uses `/NODEFAULTLIB` with the `kernel32` import library.
- Preserve the safe `write_stdout(&str)` interface when changing output.
## Build System
CMake is configured with `LANGUAGES NONE` and calls `rustc` through an
`add_custom_command`. `find_program(RUSTC rustc REQUIRED)` locates the
compiler.
Configure and build a release executable with Ninja:
```sh
cmake -S . -B build -GNinja -DCMAKE_BUILD_TYPE=Release
cmake --build build
```
Run it on Unix:
```sh
./build/no_core_write
```
Run it on Windows:
```powershell
.\build\no_core_write.exe
```
`CMAKE_BUILD_TYPE` is translated explicitly to rustc flags:
- `Release`: `-C opt-level=3 -C debuginfo=0`
- `RelWithDebInfo`: `-C opt-level=3 -C debuginfo=2`
- Other or unset configurations: `-C opt-level=0 -C debuginfo=2`
`no_core` is unstable, so CMake passes `RUSTC_BOOTSTRAP=1` to the direct
rustc invocation. A nightly compiler may be used instead, but Cargo is
still not permitted.
## Verification
After changing the source or build configuration, run a clean release build
and execute the program:
```sh
rm -rf build
cmake -S . -B build -GNinja -DCMAKE_BUILD_TYPE=Release
cmake --build build
./build/no_core_write
```
On Windows, use `Remove-Item -Recurse -Force build` and run
`build\\no_core_write.exe` instead. Confirm that stdout contains `Hello`.
When size matters, inspect the Windows executable size after a release
build. Avoid adding runtime libraries, formatting code, or Rust library
dependencies.
## Commit Messages
- Follow the 50/72 rule: subject lines are at most 50 characters and body
lines are wrapped at 72 characters.
- Use conventional prefixes such as `feat:`, `fix:`, `docs:`, `chore:`,
and `ci:`.
- Separate the subject from the body with a blank line.
- Keep commit messages concise.
- Include a `Co-Authored-By:` trailer for every agent or model that
contributed to the commit, one trailer per co-author:
```text
Co-Authored-By: qwen (lmstudio/qwen3.6-27b-mtp): wrote tests + build
Co-Authored-By: luna (openai/gpt-5.6-luna): reviewed edge cases
```
## Editing Guidelines
- Keep changes minimal and directly related to the request.
- Use four-space indentation in Rust and CMake.
- Keep normal text in Markdown within 80 columns where practical.
- Do not add speculative abstractions or dependencies.
- Update this file when the build workflow, no-core constraints, supported
platforms, or source layout changes.
+52
View File
@@ -0,0 +1,52 @@
cmake_minimum_required(VERSION 3.20)
project(no_core_write LANGUAGES NONE)
set(CMAKE_EXPORT_COMPILE_COMMANDS ON)
find_program(RUSTC rustc REQUIRED)
set(RUST_SOURCE "${CMAKE_CURRENT_SOURCE_DIR}/main.rs")
set(RUST_BINARY "${CMAKE_CURRENT_BINARY_DIR}/no_core_write${CMAKE_EXECUTABLE_SUFFIX}")
# no_core is an unstable rustc feature. RUSTC_BOOTSTRAP lets this deliberately
# minimal example be built with the installed compiler without Cargo or core.
set(RUSTC_ENV "RUSTC_BOOTSTRAP=1")
set(RUSTC_FLAGS -C panic=abort)
# CMAKE_BUILD_TYPE does not automatically affect a custom rustc command.
# Pass the corresponding optimization/debug-info settings explicitly.
if (CMAKE_BUILD_TYPE STREQUAL "Release")
list(APPEND RUSTC_FLAGS -C opt-level=3 -C debuginfo=0)
elseif (CMAKE_BUILD_TYPE STREQUAL "RelWithDebInfo")
list(APPEND RUSTC_FLAGS -C opt-level=3 -C debuginfo=2)
else()
list(APPEND RUSTC_FLAGS -C opt-level=0 -C debuginfo=2)
endif()
# A no_core binary does not get Rust's normal startup/linker setup. Link the
# small platform system/runtime library needed by the stdout implementation.
if (WIN32)
# Use Win32 directly instead of the C runtime; this keeps the executable
# small while still writing to the console's stdout handle.
set(RUSTC_LINK_ARGS
-l kernel32
-C
"link-args=/NODEFAULTLIB /ENTRY:mainCRTStartup /SUBSYSTEM:CONSOLE /OPT:REF /OPT:ICF"
)
elseif (UNIX)
set(RUSTC_LINK_ARGS -C "link-args=-lc")
endif()
add_custom_command(
OUTPUT "${RUST_BINARY}"
COMMAND "${CMAKE_COMMAND}" -E env "${RUSTC_ENV}" "${RUSTC}"
"${RUST_SOURCE}"
--crate-name no_core_write
--crate-type bin
-o "${RUST_BINARY}"
${RUSTC_FLAGS}
${RUSTC_LINK_ARGS}
DEPENDS "${RUST_SOURCE}"
VERBATIM
)
add_custom_target(no_core_write ALL DEPENDS "${RUST_BINARY}")
+95
View File
@@ -0,0 +1,95 @@
#![no_core]
#![no_main]
#![feature(no_core, lang_items, auto_traits)]
#![allow(internal_features)]
// These three marker traits are the minimum language items needed when core is
// not linked. They are compiler contracts, not a replacement for core.
#[lang = "pointee_sized"]
trait PointeeSized {}
#[lang = "meta_sized"]
trait MetaSized: PointeeSized {}
#[lang = "sized"]
trait Sized: MetaSized {}
#[lang = "copy"]
trait Copy {}
#[lang = "freeze"]
unsafe auto trait Freeze {}
#[lang = "eh_personality"]
extern "C" fn eh_personality() {}
#[lang = "panic_info"]
struct PanicInfo;
#[lang = "panic_impl"]
fn panic_impl(_: &PanicInfo) -> ! {
loop {}
}
#[lang = "panic_cannot_unwind"]
fn panic_cannot_unwind() -> ! {
loop {}
}
#[cfg(unix)]
unsafe extern "C" {
fn write(fd: i32, buffer: *const u8, count: usize) -> isize;
}
#[cfg(windows)]
unsafe extern "system" {
fn GetStdHandle(which: u32) -> *mut u8;
fn WriteFile(
handle: *mut u8,
buffer: *const u8,
count: u32,
written: *mut u32,
overlapped: *mut u8,
) -> i32;
fn ExitProcess(status: u32) -> !;
}
// A string slice is a (pointer, length) pair. This keeps the public helper
// ergonomic without importing core just to call str::as_ptr and str::len.
#[repr(C)]
union StringParts<'a> {
string: &'a str,
bytes: (*const u8, usize),
}
fn write_stdout(message: &str) {
let (buffer, length) = unsafe { StringParts { string: message }.bytes };
unsafe {
#[cfg(unix)]
{
write(1, buffer, length);
}
#[cfg(windows)]
{
let handle = GetStdHandle(0xffff_fff5); // STD_OUTPUT_HANDLE
let mut written: u32 = 0;
WriteFile(handle, buffer, length as u32, &mut written, 0 as *mut u8);
}
}
}
#[cfg(unix)]
#[no_mangle]
pub extern "C" fn main() -> i32 {
write_stdout("Hello\n");
0
}
#[cfg(windows)]
#[no_mangle]
pub extern "system" fn mainCRTStartup() -> ! {
write_stdout("Hello\n");
unsafe { ExitProcess(0) }
}