Home >Backend Development >PHP Tutorial >Create a PHP extension using Rust_PHP Tutorial
Last October, my Etsy colleagues and I had a discussion about how to write extensions for interpreted languages like PHP. The current situation of Ruby or Python should be easier than PHP. We talked about how the barrier to writing a successful extension is that they usually need to be written in C, but it's hard to have that confidence if you're not good at C.
Since then I have had the idea of writing one in Rust, and have been trying it out for the past few days. I finally got it running this morning.
Rust
My basic starting point is to write some compilable Rust code into a library, write some C header files for it, and make an extension in C for the called PHP. It's not easy, but it's fun.
Rust FFIforeign function interface)
The first thing I did was play around with Rust’s external function interface connecting Rust to C. I once wrote a flexible library using the 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.
<ol class="dp-c"><li class="alt"><span><span class="comment">// hello_from_rust.rs</span><span> </span></span></li><li><span>#![crate_type = <span class="string">"staticlib"</span><span>] </span></span></li><li class="alt"><span> </span></li><li><span>#![feature(libc)] </span></li><li class="alt"><span>extern crate libc; </span></li><li><span><span class="keyword">use</span><span> std::ffi::CStr; </span></span></li><li class="alt"><span> </span></li><li><span>#[no_mangle] </span></li><li class="alt"><span>pub extern <span class="string">"C"</span><span> fn hello_from_rust(name: *</span><span class="keyword">const</span><span> libc::c_char) { </span></span></li><li><span>let buf_name = unsafe { CStr::from_ptr(name).to_bytes() }; </span></li><li class="alt"><span>let str_name = String::from_utf8(buf_name.to_vec()).unwrap(); </span></li><li><span>let c_name = format!(<span class="string">"Hello from Rust, {}"</span><span>, str_name); </span></span></li><li class="alt"><span>println!(<span class="string">"{}"</span><span>, c_name); </span></span></li><li><span>} </span></li></ol>
I am from C or other! ) split it up using a Rust library called in . 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:
<ol class="dp-c"><li class="alt"><span><span>note: link against the following native artifacts when linking against this </span><span class="keyword">static</span><span> library </span></span></li><li><span>note: the order <span class="keyword">and</span><span> any duplication can be significant on some platforms, </span><span class="keyword">and</span><span> so may need to be preserved </span></span></li><li class="alt"><span>note: library: Systemnote: library: pthread </span></li><li><span>note: library: c </span></li><li class="alt"><span>note: library: m </span></li></ol>
This is what the Rust compiler tells us to link against when we don’t use this dependency.
Calling Rust from C
Now that we have a library, we have to do two things to make it 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:
<ol class="dp-c"><li class="alt"><span><span>note: link against the following native artifacts when linking against this </span><span class="keyword">static</span><span> library </span></span></li><li><span>note: the order <span class="keyword">and</span><span> any duplication can be significant on some platforms, </span><span class="keyword">and</span><span> so may need to be preserved </span></span></li><li class="alt"><span>note: library: Systemnote: library: pthread </span></li><li><span>note: library: c </span></li><li class="alt"><span>note: library: m </span></li></ol>
This is a fairly basic header file that just provides a signature/definition for a simple function. Next we need to write a C program and use it.
<ol class="dp-c"><li class="alt"><span><span class="comment">// hello.c</span><span> </span></span></li><li><span>#<span class="keyword">include</span><span> <stdio.h> </span></span></li><li class="alt"><span>#<span class="keyword">include</span><span> <stdlib.h> </span></span></li><li><span>#<span class="keyword">include</span><span> </span><span class="string">"hello_from_rust.h"</span><span> </span></span></li><li class="alt"><span> </span></li><li><span>int main(int argc, char *argv[]) { </span></li><li class="alt"><span>hello_from_rust(<span class="string">"Jared!"</span><span>); </span></span></li><li><span>} </span></li></ol>
We compile it by running this 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 against those "native antiques" so that the Rust compiler can provide them when compiling our Rust library.
By running the following code we can get a binary file:
<ol class="dp-c"><li class="alt"><span><span>$ ./hello_c </span></span></li><li><span>Hello from Rust, Jared! </span></li></ol>
Beautiful! We just called the Rust library from C. Now we need to understand how a Rust library gets into a PHP extension.
Call c from php
This part took me some time to figure out, the documentation is not the best in the world for php extensions. The best part is that the php source comes from bundling a script ext_skel which (mostly stands for "Extended Skeleton") i.e. generates most of the boilerplate code you need. In order to get the code running, I worked really hard to learn the php documentation, "Extended Bones".
You can get started by downloading the unquoted php source, writing the code into the php directory and running:
<ol class="dp-c"><li class="alt"><span><span>$ cd ext/ </span></span></li><li><span>$ ./ext_skel –extname=hello_from_rust </span></li></ol>
This will generate the basic skeleton needed to create the php extension. 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:
<ol class="dp-c"><li class="alt"><span><span>PHP_ARG_WITH(hello_from_rust, </span><span class="keyword">for</span><span> hello_from_rust support, </span></span></li><li><span>[ --with-hello_from_rust Include hello_from_rust support]) </span></li><li class="alt"><span> </span></li><li><span><span class="keyword">if</span><span> test </span><span class="string">"$PHP_HELLO_FROM_RUST"</span><span> != </span><span class="string">"no"</span><span>; then </span></span></li><li class="alt"><span>PHP_SUBST(HELLO_FROM_RUST_SHARED_LIBADD) </span></li><li><span> </span></li><li class="alt"><span>PHP_ADD_LIBRARY_WITH_PATH(hello_from_rust, ., HELLO_FROM_RUST_SHARED_LIBADD) </span></li><li><span> </span></li><li class="alt"><span>PHP_NEW_EXTENSION(hello_from_rust, hello_from_rust.c, <span class="vars">$ext_shared</span><span>) </span></span></li><li><span>fi </span></li></ol>
As I understand it, these are basic macro commands. But the documentation on these macro commands is quite poor. For example: google "PHP_ADD_LIBRARY_WITH_PATH" and there are no 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.
<ol class="dp-c"><li class="alt"><span><span>#</span><span class="keyword">include</span><span> </span><span class="string">"hello_from_rust.h"</span><span> </span></span></li><li><span> </span></li><li class="alt"><span><span class="comment">// a bunch of comments and code removed...</span><span> </span></span></li><li><span> </span></li><li class="alt"><span>PHP_FUNCTION(confirm_hello_from_rust_compiled) </span></li><li><span>{ </span></li><li class="alt"><span>char *arg = NULL; </span></li><li><span>int arg_len, len; </span></li><li class="alt"><span>char *strg; </span></li><li><span> </span></li><li class="alt"><span><span class="keyword">if</span><span> (zend_parse_parameters(ZEND_NUM_ARGS() TSRMLS_CC, </span><span class="string">"s"</span><span>, &arg, &arg_len) == FAILURE) { </span></span></li><li><span><span class="keyword">return</span><span>; </span></span></li><li class="alt"><span>} </span></li><li><span> </span></li><li class="alt"><span>hello_from_rust(<span class="string">"Jared (from PHP!!)!"</span><span>); </span></span></li><li><span> </span></li><li class="alt"><span>len = spprintf(&strg, 0, <span class="string">"Congratulations! You have successfully modified ext/%.78s/config.m4. Module %.78s is now compiled into PHP."</span><span>, </span><span class="string">"hello_from_rust"</span><span>, arg); </span></span></li><li><span>RETURN_STRINGL(strg, len, 0); </span></li><li class="alt"><span>} </span></li></ol>
注意:我添加了hello_from_rust(“Jared (fromPHP!!)!”);。
现在,我们可以试着建立我们的扩展:
<ol class="dp-c"><li class="alt"><span><span>$ phpize </span></span></li><li><span>$ ./configure </span></li><li class="alt"><span>$ sudo make install </span></li></ol>
就是它,生成我们的元配置,运行生成的配置命令,然后安装该扩展。安装时,我必须亲自使用sudo,因为我的用户并不拥有安装目录的 php 扩展。
现在,我们可以运行它啦!
<ol class="dp-c"><li class="alt"><span><span>$ php hello_from_rust.php </span></span></li><li><span>Functions available in the test extension: </span></li><li class="alt"><span>confirm_hello_from_rust_compiled </span></li><li><span> </span></li><li class="alt"><span>Hello from Rust, Jared (from PHP!!)! </span></li><li><span>Congratulations! You have successfully modified ext/hello_from_rust/config.m4. Module hello_from_rust is now compiled into PHP. </span></li><li class="alt"><span>Segmentation fault: 11 </span></li></ol>
还不错,php 已进入我们的 c 扩展,看到我们的应用方法列表并且调用。接着,c 扩展已进入我们的 rust 库,开始打印我们的字符串。那很有趣!但是……那段错误的结局发生了什么?
正如我所提到的,这里是使用了 Rust 相关的 println! 宏,但是我没有对它做进一步的调试。如果我们从我们的 Rust 库中删除并返回一个 char* 替代,段错误就会消失。
这里是 Rust 的代码:
<ol class="dp-c"><li class="alt"><span><span>#![crate_type = </span><span class="string">"staticlib"</span><span>] </span></span></li><li><span> </span></li><li class="alt"><span>#![feature(libc)] </span></li><li><span>extern crate libc; </span></li><li class="alt"><span><span class="keyword">use</span><span> std::ffi::{CStr, CString}; </span></span></li><li><span> </span></li><li class="alt"><span>#[no_mangle] </span></li><li><span>pub extern <span class="string">"C"</span><span> fn hello_from_rust(name: *</span><span class="keyword">const</span><span> libc::c_char) -> *</span><span class="keyword">const</span><span> libc::c_char { </span></span></li><li class="alt"><span>let buf_name = unsafe { CStr::from_ptr(name).to_bytes() }; </span></li><li><span>let str_name = String::from_utf8(buf_name.to_vec()).unwrap(); </span></li><li class="alt"><span>let c_name = format!(<span class="string">"Hello from Rust, {}"</span><span>, str_name); </span></span></li><li><span> </span></li><li class="alt"><span>CString::<span class="keyword">new</span><span>(c_name).unwrap().as_ptr() </span></span></li><li><span>} </span></li></ol>
并变更 C 头文件:
<ol class="dp-c"><li class="alt"><span><span>#ifndef __HELLO </span></span></li><li><span>#define __HELLO </span></li><li><span><span class="keyword">const</span><span> char * hello_from_rust(</span><span class="keyword">const</span><span> char *name); </span></span></li><li><span>#<span class="keyword">endif</span><span> </span></span></li></ol>
还要变更 C 扩展文件:
<ol class="dp-c"><li class="alt"><span><span>PHP_FUNCTION(confirm_hello_from_rust_compiled) </span></span></li><li><span>{ </span></li><li class="alt"><span>char *arg = NULL; </span></li><li><span>int arg_len, len; </span></li><li class="alt"><span>char *strg; </span></li><li><span> </span></li><li class="alt"><span><span class="keyword">if</span><span> (zend_parse_parameters(ZEND_NUM_ARGS() TSRMLS_CC, </span><span class="string">"s"</span><span>, &arg, &arg_len) == FAILURE) { </span></span></li><li><span><span class="keyword">return</span><span>; </span></span></li><li class="alt"><span>} </span></li><li><span> </span></li><li class="alt"><span>char *str; </span></li><li><span>str = hello_from_rust(<span class="string">"Jared (from PHP!!)!"</span><span>); </span></span></li><li class="alt"><span>printf(<span class="string">"%s/n"</span><span>, str); </span></span></li><li><span> </span></li><li class="alt"><span>len = spprintf(&strg, 0, <span class="string">"Congratulations! You have successfully modified ext/%.78s/config.m4. Module %.78s is now compiled into PHP."</span><span>, </span><span class="string">"hello_from_rust"</span><span>, arg); </span></span></li><li><span>RETURN_STRINGL(strg, len, 0); </span></li><li class="alt"><span>} </span></li></ol>
无用的微基准
那么为什么你还要这样做?我还真的没有在现实世界里使用过这个。但是我真的认为斐波那契序列算法就是一个好的例子来说明一个PHP拓展如何很基本。通常是直截了当在Ruby中):
<ol class="dp-c"><li class="alt"><span><span>def fib(at) </span><span class="keyword">do</span><span> </span></span></li><li><span><span class="keyword">if</span><span> (at == 1 || at == 0) </span></span></li><li class="alt"><span><span class="keyword">return</span><span> at </span></span></li><li><span><span class="keyword">else</span><span> </span></span></li><li class="alt"><span><span class="keyword">return</span><span> fib(at - 1) + fib(at - 2) </span></span></li><li><span><span class="func">end</span><span> </span></span></li><li class="alt"><span><span class="func">end</span><span> </span></span></li></ol>
而且可以通过不使用递归来改善这不好的性能:
<ol class="dp-c"><li class="alt"><span><span>def fib(at) </span><span class="keyword">do</span><span> </span></span></li><li><span><span class="keyword">if</span><span> (at == 1 || at == 0) </span></span></li><li class="alt"><span><span class="keyword">return</span><span> at </span></span></li><li><span>elsif (val = @cache[at]).present? </span></li><li class="alt"><span><span class="keyword">return</span><span> val </span></span></li><li><span><span class="func">end</span><span> </span></span></li><li class="alt"><span> </span></li><li><span>total = 1 </span></li><li class="alt"><span>parent = 1 </span></li><li><span>gp = 1 </span></li><li class="alt"><span> </span></li><li><span>(1..at).each <span class="keyword">do</span><span> |i| </span></span></li><li class="alt"><span>total = parent + gp </span></li><li><span>gp = parent </span></li><li class="alt"><span>parent = total </span></li><li><span><span class="func">end</span><span> </span></span></li><li class="alt"><span> </span></li><li><span><span class="keyword">return</span><span> total </span></span></li><li class="alt"><span><span class="func">end</span><span> </span></span></li></ol>
那么我们围绕它来写两个例子,一个在PHP中,一个在Rust中。看看哪个更快。下面是PHP版:
<ol class="dp-c"><li class="alt"><span><span>def fib(at) </span><span class="keyword">do</span><span> </span></span></li><li><span><span class="keyword">if</span><span> (at == 1 || at == 0) </span></span></li><li class="alt"><span><span class="keyword">return</span><span> at </span></span></li><li><span>elsif (val = @cache[at]).present? </span></li><li class="alt"><span><span class="keyword">return</span><span> val </span></span></li><li><span><span class="func">end</span><span> </span></span></li><li class="alt"><span> </span></li><li><span>total = 1 </span></li><li class="alt"><span>parent = 1 </span></li><li><span>gp = 1 </span></li><li class="alt"><span> </span></li><li><span>(1..at).each <span class="keyword">do</span><span> |i| </span></span></li><li class="alt"><span>total = parent + gp </span></li><li><span>gp = parent </span></li><li class="alt"><span>parent = total </span></li><li><span><span class="func">end</span><span> </span></span></li><li class="alt"><span> </span></li><li><span><span class="keyword">return</span><span> total </span></span></li><li class="alt"><span><span class="func">end</span><span> </span></span></li><li><span> </span></li><li class="alt"><span>这是它的运行结果: </span></li><li><span> </span></li><li class="alt"><span>$ time php php_fib.php </span></li><li><span> </span></li><li class="alt"><span>real 0m2.046s </span></li><li><span>user 0m1.823s </span></li><li class="alt"><span>sys 0m0.207s </span></li><li><span> </span></li><li class="alt"><span>现在我们来做Rust版。下面是库资源: </span></li><li><span> </span></li><li class="alt"><span>#![crate_type = <span class="string">"staticlib"</span><span>] </span></span></li><li><span> </span></li><li class="alt"><span>fn fib(at: usize) -> usize { </span></li><li><span><span class="keyword">if</span><span> at == 0 { </span></span></li><li class="alt"><span><span class="keyword">return</span><span> 0; </span></span></li><li><span>} <span class="keyword">else</span><span> </span><span class="keyword">if</span><span> at == 1 { </span></span></li><li class="alt"><span><span class="keyword">return</span><span> 1; </span></span></li><li><span>} </span></li><li class="alt"><span> </span></li><li><span>let mut total = 1; </span></li><li class="alt"><span>let mut parent = 1; </span></li><li><span>let mut gp = 0; </span></li><li class="alt"><span><span class="keyword">for</span><span> _ in 1 .. at { </span></span></li><li><span>total = parent + gp; </span></li><li class="alt"><span>gp = parent; </span></li><li><span>parent = total; </span></li><li class="alt"><span>} </span></li><li><span> </span></li><li class="alt"><span><span class="keyword">return</span><span> total; </span></span></li><li><span>} </span></li><li class="alt"><span> </span></li><li><span>#[no_mangle] </span></li><li class="alt"><span>pub extern <span class="string">"C"</span><span> fn rust_fib(at: usize) -> usize { </span></span></li><li><span>fib(at) </span></li><li class="alt"><span>} </span></li><li><span> </span></li><li class="alt"><span>注意,我编译的库rustc – O rust_lib.rs使编译器优化因为我们是这里的标准)。这里是C扩展源相关摘录): </span></li><li><span> </span></li><li class="alt"><span>PHP_FUNCTION(confirm_rust_fib_compiled) </span></li><li><span>{ </span></li><li class="alt"><span>long number; </span></li><li><span> </span></li><li class="alt"><span><span class="keyword">if</span><span> (zend_parse_parameters(ZEND_NUM_ARGS() TSRMLS_CC, </span><span class="string">"l"</span><span>, &number) == FAILURE) { </span></span></li><li><span><span class="keyword">return</span><span>; </span></span></li><li class="alt"><span>} </span></li><li><span> </span></li><li class="alt"><span>RETURN_LONG(rust_fib(number)); </span></li><li><span>} </span></li></ol>
运行PHP脚本:
<ol class="dp-c"><li class="alt"><span><span><?php </span></span></li><li><span><span class="vars">$br</span><span> = (php_sapi_name() == </span><span class="string">"cli"</span><span>)? </span><span class="string">""</span><span>:</span><span class="string">"<br>"</span><span>; </span></span></li><li class="alt"><span> </span></li><li><span><span class="keyword">if</span><span>(!</span><span class="func">extension_loaded</span><span>(</span><span class="string">'rust_fib'</span><span>)) { </span></span></li><li class="alt"><span>dl(<span class="string">'rust_fib.'</span><span> . PHP_SHLIB_SUFFIX); </span></span></li><li><span>} </span></li><li class="alt"><span> </span></li><li><span><span class="keyword">for</span><span> (</span><span class="vars">$i</span><span> = 0; </span><span class="vars">$i</span><span> < 100000; </span><span class="vars">$i</span><span> ++) { </span></span></li><li class="alt"><span>confirm_rust_fib_compiled(92); </span></li><li><span>} </span></li><li class="alt"><span>?> </span></li><li><span> </span></li><li class="alt"><span>这就是它的运行结果: </span></li><li><span> </span></li><li class="alt"><span>$ time php rust_fib.php </span></li><li><span> </span></li><li class="alt"><span>real 0m0.586s </span></li><li><span>user 0m0.342s </span></li><li class="alt"><span>sys 0m0.221s </span></li></ol>
你可以看见它比前者快了三倍!完美的Rust微基准!
总结
这里几乎没有得出什么结论。我不确定在Rust上写一个PHP的扩展是一个好的想法,但是花费一些时间去研究Rust,PHP和C,这是一个很好的方式。
如果你希望查看所有代码或者查看更改记录,可以访问GitHub Repo。