Home >Backend Development >PHP Tutorial >PHP extension to Rust
This article mainly introduces how to use Rust extensions in PHP programs. Rust is a recently emerging compiled language with outstanding performance. I hope to be helpful.
Rust in C or PHP
My basic starting point is to write some compilable Rust code into a library and write some C header files for it , make an extension in C for the called PHP. It's not easy, but it's fun.
Rust FFI (foreign function interface)
The first thing I did was play around with Rust’s foreign function interface connecting Rust to C. I once wrote a flexible library using a simple method (hello_from_rust) with a single declaration (a pointer to a C char, otherwise known as a string). Here is the output of "Hello from Rust" after input.
// hello_from_rust.rs #![crate_type = "staticlib"] #![feature(libc)] extern crate libc; use std::ffi::CStr; #[no_mangle] pub extern "C" fn hello_from_rust(name: *const libc::c_char) { let buf_name = unsafe { CStr::from_ptr(name).to_bytes() }; let str_name = String::from_utf8(buf_name.to_vec()).unwrap(); let c_name = format!("Hello from Rust, {}", str_name); println!("{}", c_name); }
I split it from a Rust library called from C (or other!). Here's a good explanation of what comes next.
Compiling it will get a file of .a, libhello_from_rust.a. This is a static library that contains all its own dependencies, and we link it when compiling a C program, which allows us to do subsequent things. Note: After we compile, we will get the following output:
note: link against the following native artifacts when linking against this static library note: the order and any duplication can be significant on some platforms, and so may need to be preserved note: library: Systemnote: library: pthread note: library: c note: library: m
This is what the Rust compiler does when we don’t use this dependency Tell us what needs to be linked.
Calling Rust from C
Now that we have a library, we have to do two things to ensure that it is callable from C. First, we need to create a C header file for it, hello_from_rust.h. Then link to it when we compile.
The following is the header file:
// hello_from_rust.h #ifndef __HELLO #define __HELLO void hello_from_rust(const char *name); #endif
This is a fairly basic header file, just for a simple Functions provide signatures/definitions. Next we need to write a C program and use it.
// hello.c #include <stdio.h> #include <stdlib.h> #include "hello_from_rust.h" int main(int argc, char *argv[]) { hello_from_rust("Jared!"); }
We compile it by running the code:
gcc -Wall -o hello_c hello.c -L /Users/jmcfarland/code/rust/php-hello-rust -lhello_from_rust -lSystem -lpthread -lc -lm
Note that the -lSystem -lpthread -lc -lm at the end tells gcc not to link those "local antiques" so that the Rust compiler can provide them when compiling our Rust library.
We can get a binary file by running the following code:
$ ./hello_c Hello from Rust, Jared!
Beautiful! We just called the Rust library from C. Now we need to understand how a Rust library gets into a PHP extension.
Calling c from php
This part took me a while to figure out and the documentation is not the best in the world for php extensions OK The best part is that the php source comes from bundling a script ext_skel (mostly stands for "Extended Skeleton") which generates most of the boilerplate code you need. You can get started by downloading, and unquoted, the php source, writing the code into the php directory and running:
$ cd ext/ $ ./ext_skel --extname=hello_from_rust
This will generate the code needed to create the php extension Basic skeleton. Now, move the folders everywhere you want to keep your extensions locally. And move your
.rust source
.rust library
.c header
Enter the same directory. So now you should look at a directory like this:
. ├── CREDITS ├── EXPERIMENTAL ├── config.m4 ├── config.w32 ├── hello_from_rust.c ├── hello_from_rust.h ├── hello_from_rust.php ├── hello_from_rust.rs ├── libhello_from_rust.a ├── php_hello_from_rust.h └── tests └── 001.phpt
One directory, 11 files
You can see a good description of these files in the php docs above. Create an extended file. We'll get started by editing config.m4.
Without explanation, here are my results:
PHP_ARG_WITH(hello_from_rust, for hello_from_rust support, [ --with-hello_from_rust Include hello_from_rust support]) if test "$PHP_HELLO_FROM_RUST" != "no"; then PHP_SUBST(HELLO_FROM_RUST_SHARED_LIBADD) PHP_ADD_LIBRARY_WITH_PATH(hello_from_rust, ., HELLO_FROM_RUST_SHARED_LIBADD) PHP_NEW_EXTENSION(hello_from_rust, hello_from_rust.c, $ext_shared) fi
As I understand it, these are the basics macro command. But the documentation on these macros is quite poor (for example: google "PHP_ADD_LIBRARY_WITH_PATH" does not show results written by the PHP team). I came across this PHP_ADD_LIBRARY_PATH macro in a previous thread where someone was talking about linking a static library in a PHP extension. The other recommended macros in the comments were generated after I ran ext_skel.
Now that we have the configuration setup, we need to actually call the library from the PHP script. To do this we have to modify the automatically generated file, hello_from_rust.c. First we add the hello_from_rust.h header file to the include command. Then we need to modify the definition method of confirm_hello_from_rust_compiled.
#include "hello_from_rust.h" // a bunch of comments and code removed... PHP_FUNCTION(confirm_hello_from_rust_compiled) { char *arg = NULL; int arg_len, len; char *strg; if (zend_parse_parameters(ZEND_NUM_ARGS() TSRMLS_CC, "s", &arg, &arg_len) == FAILURE) { return; } hello_from_rust("Jared (from PHP!!)!"); len = spprintf(&strg, 0, "Congratulations! You have successfully modified ext/%.78s/config.m4. Module %.78s is now compiled into PHP.", "hello_from_rust", arg); RETURN_STRINGL(strg, len, 0); }
Note: I added hello_from_rust("Jared (fromPHP!!)!");.
Now, we can try to build our extension:
$ phpize $ ./configure $ sudo make install
That’s it, generate our meta configuration, run build configuration command and then install the extension. When installing, I had to use sudo myself because my user did not own the php extension for the installation directory.
Now, we can run it!
$ php hello_from_rust.php Functions available in the test extension: confirm_hello_from_rust_compiled Hello from Rust, Jared (from PHP!!)! Congratulations! You have successfully modified ext/hello_from_rust/config.m4. Module hello_from_rust is now compiled into PHP. Segmentation fault: 11
还不错,php 已进入我们的 c 扩展,看到我们的应用方法列表并且调用。接着,c 扩展已进入我们的 rust 库,开始打印我们的字符串。那很有趣!但是......那段错误的结局发生了什么?
正如我所提到的,这里是使用了 Rust 相关的 println! 宏,但是我没有对它做进一步的调试。如果我们从我们的 Rust 库中删除并返回一个 char* 替代,段错误就会消失。
这里是 Rust 的代码:
#![crate_type = "staticlib"] #![feature(libc)] extern crate libc; use std::ffi::{CStr, CString}; #[no_mangle] pub extern "C" fn hello_from_rust(name: *const libc::c_char) -> *const libc::c_char { let buf_name = unsafe { CStr::from_ptr(name).to_bytes() }; let str_name = String::from_utf8(buf_name.to_vec()).unwrap(); let c_name = format!("Hello from Rust, {}", str_name); CString::new(c_name).unwrap().as_ptr() }
并变更 C 头文件:
#ifndef __HELLO #define __HELLO const char * hello_from_rust(const char *name); #endif
还要变更 C 扩展文件:
PHP_FUNCTION(confirm_hello_from_rust_compiled) { char *arg = NULL; int arg_len, len; char *strg; if (zend_parse_parameters(ZEND_NUM_ARGS() TSRMLS_CC, "s", &arg, &arg_len) == FAILURE) { return; } char *str; str = hello_from_rust("Jared (from PHP!!)!"); printf("%s\n", str); len = spprintf(&strg, 0, "Congratulations! You have successfully modified ext/%.78s/config.m4. Module %.78s is now compiled into PHP.", "hello_from_rust", arg); RETURN_STRINGL(strg, len, 0); }
无用的微基准
那么为什么你还要这样做?我还真的没有在现实世界里使用过这个。但是我真的认为斐波那契序列算法就是一个好的例子来说明一个PHP拓展如何很基本。通常是直截了当(在Ruby中):
def fib(at) do if (at == 1 || at == 0) return at else return fib(at - 1) + fib(at - 2) end end
而且可以通过不使用递归来改善这不好的性能:
def fib(at) do if (at == 1 || at == 0) return at elsif (val = @cache[at]).present? return val end total = 1 parent = 1 gp = 1 (1..at).each do |i| total = parent + gp gp = parent parent = total end return total end
那么我们围绕它来写两个例子,一个在PHP中,一个在Rust中。看看哪个更快。下面是PHP版:
def fib(at) do if (at == 1 || at == 0) return at elsif (val = @cache[at]).present? return val end total = 1 parent = 1 gp = 1 (1..at).each do |i| total = parent + gp gp = parent parent = total end return total end
这是它的运行结果:
$ time php php_fib.php real 0m2.046s user 0m1.823s sys 0m0.207s
现在我们来做Rust版。下面是库资源:
#![crate_type = "staticlib"] fn fib(at: usize) -> usize { if at == 0 { return 0; } else if at == 1 { return 1; } let mut total = 1; let mut parent = 1; let mut gp = 0; for _ in 1 .. at { total = parent + gp; gp = parent; parent = total; } return total; } #[no_mangle] pub extern "C" fn rust_fib(at: usize) -> usize { fib(at) }
注意,我编译的库rustc - O rust_lib.rs使编译器优化(因为我们是这里的标准)。这里是C扩展源(相关摘录):
PHP_FUNCTION(confirm_rust_fib_compiled) { long number; if (zend_parse_parameters(ZEND_NUM_ARGS() TSRMLS_CC, "l", &number) == FAILURE) { return; } RETURN_LONG(rust_fib(number)); }
运行PHP脚本:
<?php $br = (php_sapi_name() == "cli")? "":"<br>"; if(!extension_loaded('rust_fib')) { dl('rust_fib.' . PHP_SHLIB_SUFFIX); } for ($i = 0; $i < 100000; $i ++) { confirm_rust_fib_compiled(92); } ?>
这就是它的运行结果:
$ time php rust_fib.php real 0m0.586s user 0m0.342s sys 0m0.221s
你可以看见它比前者快了三倍!完美的Rust微基准!
相关推荐:
macOS 中使用 phpize 动态添加 PHP 扩展的错误解决方法
The above is the detailed content of PHP extension to Rust. For more information, please follow other related articles on the PHP Chinese website!