Last October, my colleagues and I had a discussion about how to write a Etsy for PHP-like explanatory languages, and Ruby or Python should now be easier than PHP. We talked about writing a successful creation of the extension of the barrier is that they usually need to write in C, but if you are not good at C this language, it is difficult to have that confidence.
Since then I have had the idea of writing with Rust, which has been trying for the past few days. I finally let it run this morning.
C or Rust in PHP
My basic starting point is to write some of the rust code can be compiled into a library, and write it some C header file, in C for the called PHP to do an extension. It's not very simple, but it's fun.
Rust FFI (Foreign function interface)
The first thing I did was to fiddle with the external function interfaces of rust and the C-linked rust. 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), followed by the input output "Hello from rust".
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!) ) to split it in the Rust library called. Here's a good explanation for what happens next.
Compiling it will get a file of. A, libhello_from_rust.a. This is a static library that contains all of its own dependencies, and we link it when compiling a C program, which allows us to do the following things. Note: After we compile we will get the following output:
Note:link against the following native artifacts when linking against this static librarynote:the order and any Duplicat Ion can is significant on some platforms, and so could need to be preservednote:library:Systemnote:library:pthreadnote: Library:cnote:library:m
This is where the rust compiler tells us what to link 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 sure 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.
Here 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 that provides signature/definition only for a simple function. Then 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 at the end of the-LSYSTEM-LPTHREAD-LC-LM tells GCC not to link those "local antiques", in order to compile our rust library when the rust compiler can provide it.
By running the following code we can get 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 goes into a php extension.
Calling C from PHP
It took me some time to figure out that in this world, this document is not the best in PHP extensions. The best part is from a PHP source that binds a script Ext_skel (most of which represents the "extended skeleton") that generates most of the boilerplate code you need. To get the code running, I worked very the PHP documentation, Extending the skeleton.
You can start with the download, and the non-quota PHP source, write the code into the PHP directory and run:
$ CD ext/
$./ext_skel–extname=hello_from_rust
This will generate the basic skeleton needed to create the PHP extension. Now, move your folders everywhere you want to keep your extensions in place. and move your
. Rust Source
. Rust Library
. C Header
into 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 PHP docs above. Create an extended file. We're going to start by editing config.m4.
Not explained, here is my result:
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 basic macro commands. But the documentation for these macro commands is pretty bad (for example: Google "Php_add_library_with_path" doesn't show the results that the PHP team wrote). I stumbled upon this Php_add_library_path macro command in a previous thread that some people are talking about linking a static library in a PHP extension. Other recommended macro commands in the comments are generated after I run the Ext_skel.
Now that we have the configuration settings, we need to actually call the library from the PHP script. For this we have to modify the automatically generated files, hello_from_rust.c. First we add the Hello_from_rust.h header file to the containing command. Then we need to modify the Confirm_hello_from_rust_compiled definition method.
#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 extensions:
$ phpize
$./configure
$ sudo make install
That's it, build our meta configuration, run the generated configuration command, and then install the extension. When installing, I have to use sudo myself because my users do not own the PHP extension of 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 to PHP.
Segmentation fault:11
Also good, PHP has entered our C extension, see our list of application methods and call. Then, the C extension has entered our rust library and begins printing our string. That's funny! But...... What happened to the wrong ending?
As I mentioned, this is the use of Rust-related println! Macro, but I didn't do any further debugging on it. If we remove from our Rust library and return a char* substitution, the segment error disappears.
Here's the Rust code:
#! [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
Also 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 doing this? I really didn't use this in the real world. But I really think that the Fibonacci sequence algorithm is a good example of how basic a php extension is. is usually straightforward (in Ruby):
def fib (at) does if (at = = 1 | | at = = 0) return at else return fib (at-1) + fib (at-2) endend
And you can improve this poor performance by not using recursive returns:
def fib (at) does 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
So we're going to write two examples around it, one in PHP and one in rust. See which one is faster. Here is the PHP version:
def fib (at) does 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
This is the result of its operation:
$ time php php_fib.phpreal 0m2.046suser 0m1.823ssys 0m0.207s
Now let's do the rust version. Here is the Library resource:
#! [Crate_type = "Staticlib"]fn fib (at:usize), usize { if at = = 0 { return 0; } else if at = = 1 { retur n 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 I compiled the library rustc–o rust_lib.rs to make the compiler optimized (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));}
To run the PHP script:
<?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);}? >
This is the result of its operation:
$ time php rust_fib.phpreal 0m0.586suser 0m0.342ssys 0m0.221s
You can see it three times times faster than the former! Perfect Rust micro-benchmark!
Summarize
There is little conclusion to be drawn here. I'm not sure it's a good idea to write a php extension on rust, but it's a good way to spend some time studying rust,php and C.