Bridging Rust and PHP with whyNot: A Learner’s Journey
When you’re learning a new language, the temptation is to start small: toy projects, simple exercises, maybe a “Hello World” or two. But I’ve always believed the best way to learn is to build something real, something that scratches an itch and forces you to wrestle with the language in practice. That’s how whyNot came to life — a Rust ↔ PHP bridge that lets you call PHP functions directly from Rust, capture return values, printed output, and exceptions, with async support via threads or Tokio.
I’m still learning Rust myself, and whyNot is as much a learning experiment as it is a tool. But it’s already proving useful, and I want to share the story, the features, and the lessons I’ve learned along the way.
Why I Built whyNot
PHP has been my bread and butter for years. I love PHP — it’s the language I’m most comfortable with, the one I’ve used to build countless projects, and the one I trust when I need to get things done quickly. It powers much of the web, from WordPress to Laravel to legacy enterprise systems. But PHP has limits, especially when it comes to performance and modern concurrency. Rust, on the other hand, is fast, safe, and designed for systems programming. The idea of combining them fascinated me.
Instead of rewriting PHP applications in Rust (which is unrealistic for most teams), I thought: why not build a bridge? Why not let Rust call into PHP directly, so developers can leverage existing PHP logic while experimenting with Rust’s ecosystem? And for me personally, why not use this as a fun way to learn Rust by building something ambitious?
That’s the spirit behind the name: whyNot. It’s both a challenge and an invitation.
What whyNot Can Do
Here’s what the crate currently supports:
- Call any PHP function from Rust
- Capture return values and printed output
- Structured exception handling with
PhpException - Objects returned with class + fields
- Persistent runtime (globals/includes survive across calls)
-
includeandevalsupport - Async via threads (
call_async_with_cfg) - Async/await via Tokio (
--features async_tokio)
This means you can write PHP functions in a bootstrap.php file, then call them from Rust as if they were native. You can capture not just the return value, but also whatever the PHP function prints, and handle exceptions in a structured way.
🚀 Getting Started
Installation
Add whyNot to your project:
cargo add whynot
Or in your Cargo.toml:
[dependencies]
whynot = "0.1.1"
You’ll also need PHP CLI (php) available in your PATH.
Initialize PHP scaffolding
After installing the crate, install the CLI binary:
cargo install whynot
Then run:
whynot-init
This creates a php/ folder in your project root with:
-
runner.php— the bridge script used internally by whyNot -
bootstrap.php— where you define your own PHP functions/classes
Edit bootstrap.php to add your PHP logic. The Rust side will call into these functions.
Usage Examples
Basic Call
bootstrap.php:
<?php
function add($a, $b) {
echo "Adding $a + $b\n";
return $a + $b;
}
Rust:
use whynot::{new_runtime, PhpRuntime, RuntimeConfig, RuntimeKind};
fn main() {
let cfg = RuntimeConfig::default();
let mut rt = new_runtime(RuntimeKind::Process(cfg)).unwrap();
let result = rt.call("add", &[7.into(), 5.into()]).unwrap();
println!("add.result = {:?}", result.result);
println!("add.output = {:?}", result.output);
}
Output:
add.result = Some(12)
add.output = "Adding 7 + 5\n"
Exception Handling
bootstrap.php:
<?php
function risky() {
throw new Exception("Something went wrong!");
}
Rust:
let boom = rt.call("risky", &[]).unwrap();
if let Some(ex) = boom.exception {
println!("Exception: {} ({})", ex.message, ex.class);
println!("Trace: {}", ex.trace);
}
Async with Threads
use whynot::process::ProcRuntime;
let h1 = ProcRuntime::call_async_with_cfg(cfg.clone(), "greet".to_string(), vec!["Milton".into()]);
let h2 = ProcRuntime::call_async_with_cfg(cfg.clone(), "add".to_string(), vec![7.into(), 9.into()]);
println!("greet = {:?}", h1.join().unwrap().unwrap().result);
println!("add = {:?}", h2.join().unwrap().unwrap().result);
Async/Await with Tokio
Enable the feature:
cargo run --example tokio_async --features async_tokio
Example:
#[tokio::main]
async fn main() {
let cfg = RuntimeConfig::default();
let greet = whynot::async_tokio::call_async(cfg.clone(), "greet".to_string(), vec!["Milton".into()])
.await.unwrap();
println!("greet.result = {:?}", greet.result);
}
Project Layout
whynot/
Cargo.toml
src/
lib.rs
value.rs
macros.rs
process.rs
embedded.rs
async_tokio.rs
php/
runner.php
bootstrap.php
examples/
call_any.rs
async_calls.rs
tokio_async.rs
Roadmap
- Function calls, output capture, exceptions ✅
- Object serialization ✅
- Includes, eval ✅
- Threaded async ✅
- Tokio async/await ✅
- Embedded Zend runtime (in‑process) 🚧
- Resource/extension support 🚧
- Automatic Rust ↔ PHP struct mapping 🚧
The long-term vision is to make Rust ↔ PHP interop seamless, with support for complex data structures and embedded runtimes.
Learning Rust Through Building
I want to emphasize something: I’m still learning Rust. whyNot wasn’t built from a place of mastery, but from curiosity. I struggled with FFI, lifetimes, and async patterns. I broke things constantly. But each challenge taught me something new and i hope i will get good enough to make it better.
Rust’s strictness forced me to think differently about safety and concurrency. PHP reminded me of pragmatism and flexibility. And because I genuinely love PHP and feel comfortable with it, I wanted this bridge to exist. For me, whyNot is about combining the language I know best with the one I’m still learning, and discovering what happens when they meet.
Closing Thoughts
whyNot is still evolving, but it already opens up exciting possibilities: calling PHP functions from Rust, capturing output, handling exceptions, and running async tasks. For PHP developers curious about Rust, it’s a practical gateway. For Rust learners like me, it’s a hands-on way to explore the language while building something useful.
👉 Source code: GitHub (github.com)
👉 Crate: crates.io
👉 Original post: Medium
Top comments (0)