In last October, I had a discussion with Etsy about how to expand writing for an explanatory language like PHP. Ruby or Python should be more current than PHP. We have talked about the obstacle to writing a program that successfully creates Extensions. they usually need to be written in C, but if you are not good at C, it is hard to have that confidence. In last October, I had a discussion with Etsy about how to expand writing for an explanatory language like PHP. Ruby or Python should be more current than PHP. We have talked about the obstacle to writing a program that successfully creates Extensions. they usually need to be written in C, but if you are not good at C, it is hard to have that confidence.
Since then, I have come up with the idea of writing a code with Rust, which has been trying for the past few days. I finally made it run this morning.
Rust in C or PHP
My basic starting point is to write some compiled Rust code into a library, write some C header files for it, and make an extension for the called PHP in C. Although not very simple, it is very interesting.
Rust FFI (foreign function interface)
The first thing I do is to play with the external function interfaces of Rust connected to C. I used a simple method (hello_from_rust) to write a flexible library with a single declaration (a pointer to a C char, otherwise known as a string ), the following is the "Hello from Rust" output after the 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'm from C (or other !) The Rust Library called in split it. The following is a good explanation.
Compile it to get a. a file, libhello_from_rust.a. This is a static library that contains all its dependencies, and we link it when compiling a C program, which allows us to do the following. Note: After compilation, we will get the following output:
note: link against the following native artifacts when linking against this static librarynote: the order and any duplication can be significant on some platforms, and so may need to be preservednote: library: Systemnote: library: pthreadnote: library: cnote: library: m
This is what the Rust compiler tells us when we don't use this dependency.
Call Rust from C
Since we have a library, we have to do two things to ensure that it can be called from C. First, we need to create a C header file for it, hello_from_rust.h. Then link to it during compilation.
The following is the header file:
// hello_from_rust.h#ifndef __HELLO#define __HELLOvoid hello_from_rust(const char *name);#endif
This is a fairly basic header file. it only provides signatures/definitions for a simple function. Then we need to write a C program and use it.
// hello.c#include
#include
#include "hello_from_rust.h"int main(int argc, char *argv[]) { hello_from_rust("Jared!");}
Run the code to compile it:
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" to provide the Rust compiler when compiling our Rust Library.
Run the following code to obtain a binary file:
$ ./hello_cHello from Rust, Jared!
Pretty! We just called the Rust Library from C. Now we need to understand how the Rust Library enters a PHP extension.
Call c from php
It took me some time to understand that in this world, this document is not the best in php extensions. The best part is that php sources (most of which represent the "extension skeleton") bound to a script ext_skel generate most of the sample code you need. In order to let the code run, I have worked very hard to learn the php document, "expanding the skeleton ".
You can start by downloading and write the code into the php directory and run the following code:
$ Cd ext/
$./Ext_skel-extname = hello_from_rust
This will generate the basic skeleton for creating php extensions. Now, move the folder that you want to partially keep your extension. 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 and 11 files
You can see a good description of these files in php docs. Create an extended file. We will start by editing config. m4.
Without explanation, the following are my achievements:
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, these are basic macro commands. However, the documentation on these macro commands is rather bad (for example, google "PHP_ADD_LIBRARY_WITH_PATH" does not show the results written by the PHP team ). I happen to have this PHP_ADD_LIBRARY_PATH Macro command in some people talk about connecting to the previous thread of a static library in a PHP extension. Other recommended macro commands in the comments are generated after I run ext_skel.
Since we have made configuration settings, we need to actually call the library from the PHP script. For this reason, we need to modify the automatically generated file hello_from_rust.c. First, add the header file hello_from_rust.h 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
It generates our meta configuration, runs the generated configuration command, and then installs the extension. During installation, I must use sudo myself because my users do not have php extensions 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
Pretty good. php has entered our c extension. we can see our application method list and call it. Then, the c extension has entered our rust Library and started printing our strings. That's interesting! But ...... What happened to the wrong ending?
As I mentioned, the Rust-related println is used here! Macro, but I did not perform further debugging on it. If we delete it from our Rust Library and return a char * replacement, the segment error will disappear.
Here is the code of 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()}
And change the C header file:
#ifndef __HELLO#define __HELLOconst char * hello_from_rust(const char *name);#endif
You also need to change the C extension file:
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);}
Useless micro-benchmark
So why are you still doing this? I have never used this in the real world. But I really think the Fibonacci sequence algorithm is a good example to illustrate how PHP expansion works. Usually straightforward (in Ruby ):
def fib(at) do if (at == 1 || at == 0) return at else return fib(at - 1) + fib(at - 2) endend
In addition, this performance can be improved without recursion:
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 totalend
Let's write two examples around it, one in PHP and the other in Rust. See which one is faster. Below is the PHP version:
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 totalend
Here is the running result:
$ time php php_fib.phpreal 0m2.046suser 0m1.823ssys 0m0.207s
Now let's make the Rust version. Below are the library resources:
#![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)}
Note that the compiled Library rustc-O rust_lib.rs enables compiler optimization (because we are the standard here ). Here is the C extension source (related excerpt ):
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));}
Run the PHP script:
";if(!extension_loaded('rust_fib')) { dl('rust_fib.' . PHP_SHLIB_SUFFIX);}for ($i = 0; $i < 100000; $i ++) { confirm_rust_fib_compiled(92);}?>
This is the running result:
$ time php rust_fib.phpreal 0m0.586suser 0m0.342ssys 0m0.221s
You can see that it is three times faster than the former! Perfect Rust micro benchmark!
Summary
There is almost no conclusion here. I'm not sure that writing a PHP extension on Rust is a good idea, but it is a good way to spend some time studying Rust, PHP, and C.