質問をすることでしか得られない、回答やアドバイスがある。

15分調べてもわからないことは、質問しよう!

ただいまの
回答率

88.78%

Rust内でC言語のprintfを実現したい。

解決済

回答 4

投稿 編集

  • 評価
  • クリップ 1
  • VIEW 763

jacky

score 45

Rust言語上で、C言語のprintfを実現したい。

Windows上でRustをコンパイルする際に、
Rust言語上で、printfを使いたくて以下のようにコードを記述していますが、

error: linking with `link.exe` failed: exit code: 1120

というエラーがでて、コンパイルできません。

printf関数は下記のように定義しております。

printfのシグネチャは
https://docs.rs/printf/0.1.0/printf/
上記より参照しています。

pub fn printf_c_string(output : Vec<u8>) -> isize {

    unsafe {
        extern "C" {
            fn printf(format: *const c_char, args: *mut c_void) -> String;
        }

        let c_percent = (CString::new("%s".to_string()).unwrap().as_ptr()) as *const c_char;

        let c_string = (CString::new(output).unwrap().as_ptr()) as *mut c_void;

        printf(c_percent, c_string);
    }
    return -1;
}


fn main () 
{

    let v : Vec<u8> = Vec::new();
    printf_c_string(v);

}

ちなみに、
cargo.tomlファイルは下記のように定義しています。

[package]
name = "rust_ishell"
version = "0.1.0"

edition = "2018"

# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html

[dependencies]
ctrlc = "*"
tempfile = "*"
tempdir = "*"
cstr_core = "*"
libc = "*"
printf = "*"
printf-rs = "*"

[profile.release]
opt-level = 3

ただ、Rustのマニュアルにある、fputs関数は、下記のように記述したところ
動作しています。

pub
fn print_c_string(output :Vec<u8>) -> isize {
    unsafe {
        extern "C" {
            fn puts(s: *const c_char) -> c_int;
        }
        // Vectorのサイズを取得
        let output_size: isize = output.len() as isize;

        // VectorからCStringを生成
        let to_print = CString::new(output);
        // check_type(&to_print);

        // 無事にCStringを取り出せたとき
        if (to_print.is_ok() == true) {
            puts(to_print.unwrap().as_ptr());
            return output_size;
        } else {
            panic!("{}", to_print.unwrap_err())
        }
        return -1;
    }
}

fn main() 
{
    let v = Vec::new();
    print_c_string(v);
}

なぜこのように、Cのprintfに固執する理由は、
RustでUTF-8以外の文字列を読み込みたいのです。

上記の Cのfputs関数ですと、

let b = fs::File::open(path).unwrap().bytes();

などでbyte単位で読み出して、Vec<u8>としてとりだして、
as_ptr()で puts関数に渡してやると、Shift_jisのままプロンプトで表記ができるのです。

どんなたか、Rust言語上でCのprintfを実現するための方法をご存知でしたらご教授くださいませ。

  • 気になる質問をクリップする

    クリップした質問は、後からいつでもマイページで確認できます。

    またクリップした質問に回答があった際、通知やメールを受け取ることができます。

    クリップを取り消します

  • 良い質問の評価を上げる

    以下のような質問は評価を上げましょう

    • 質問内容が明確
    • 自分も答えを知りたい
    • 質問者以外のユーザにも役立つ

    評価が高い質問は、TOPページの「注目」タブのフィードに表示されやすくなります。

    質問の評価を上げたことを取り消します

  • 評価を下げられる数の上限に達しました

    評価を下げることができません

    • 1日5回まで評価を下げられます
    • 1日に1ユーザに対して2回まで評価を下げられます

    質問の評価を下げる

    teratailでは下記のような質問を「具体的に困っていることがない質問」、「サイトポリシーに違反する質問」と定義し、推奨していません。

    • プログラミングに関係のない質問
    • やってほしいことだけを記載した丸投げの質問
    • 問題・課題が含まれていない質問
    • 意図的に内容が抹消された質問
    • 過去に投稿した質問と同じ内容の質問
    • 広告と受け取られるような投稿

    評価が下がると、TOPページの「アクティブ」「注目」タブのフィードに表示されにくくなります。

    質問の評価を下げたことを取り消します

    この機能は開放されていません

    評価を下げる条件を満たしてません

    評価を下げる理由を選択してください

    詳細な説明はこちら

    上記に当てはまらず、質問内容が明確になっていない質問には「情報の追加・修正依頼」機能からコメントをしてください。

    質問の評価を下げる機能の利用条件

    この機能を利用するためには、以下の事項を行う必要があります。

回答 4

+4

RustでUTF-8以外の文字列を読み込みたいのです。
Shift_jisのままプロンプトで表記ができるのです。

そのためだけだとしたら、encoding_rsを使えばいいと思います。

use std::io::{self, Write};
use encoding_rs::*;

fn main() -> io::Result<()> {
  // shift_jis を読み込む
  let path = "/path/to/file";
  let b: Vec<u8> = fs::File::open(path).read()?;
  let (cow, _encoding_used_in, _had_errors_in) = SHIFT_JIS.decode(b);
  let s: String = cow.into_owned();

  // sにしたいことをする

  // shift_jis にエンコードして出力する
  let (cow_out, _encoding_used_out, _had_errors_out) = SHIFT_JIS.encode(s);
  let stdout = io::stdout();
  let mut handle = stdout.lock();
  handle.write_all(cow_out)?;
}

投稿

編集

  • 回答の評価を上げる

    以下のような回答は評価を上げましょう

    • 正しい回答
    • わかりやすい回答
    • ためになる回答

    評価が高い回答ほどページの上位に表示されます。

  • 回答の評価を下げる

    下記のような回答は推奨されていません。

    • 間違っている回答
    • 質問の回答になっていない投稿
    • スパムや攻撃的な表現を用いた投稿

    評価を下げる際はその理由を明確に伝え、適切な回答に修正してもらいましょう。

checkベストアンサー

+2

RustでShift_JISを扱う場合、すでに回答のある通りencoding_rsを使うのがいいと思いますが、
元々のエラーについてもご説明します。

Windows(おそらくMSVCをお使いだと思います)では、printfstdio.hにインラインで定義されるように
なっているので、リンク時に参照するライブラリには存在せず、それが冒頭のリンクエラーになります。
ただし、昔のMSVCではライブラリに存在したらしく、そのころとの互換性のために現在でもlegacy_stdio_definitions.libというライブラリでprintfが提供されています。
従ってこれをRustからリンクすることで、printfを呼ぶことができます。

手元のWindows環境で動作したコードは以下の通りで、修正点は3か所です。

  • printfの戻り値を修正(String -> c_int
  • #[link(name="legacy_stdio_definitions", kind="static")]を追加
  • CStringのライフタイム(moshさんがご指摘の件)
use std::ffi::CString;
use std::os::raw::{c_char, c_int, c_void};

pub fn printf_c_string(output: Vec<u8>) -> isize {
    unsafe {
        #[link(name="legacy_stdio_definitions", kind="static")]
        extern "C" {
            fn printf(format: *const c_char, args: *mut c_void) -> c_int;
        }

        let c_percent = CString::new("%s".to_string()).unwrap();
        let c_percent_ptr = c_percent.as_ptr() as *const c_char;

        let c_string = CString::new(output).unwrap();
        let c_string_ptr = c_string.as_ptr() as *mut c_void;

        printf(c_percent_ptr, c_string_ptr);
    }
    return -1;
}

fn main() {
    let mut v: Vec<u8> = Vec::new();
    v.push('a' as u8);
    v.push('a' as u8);
    printf_c_string(v);
}

投稿

  • 回答の評価を上げる

    以下のような回答は評価を上げましょう

    • 正しい回答
    • わかりやすい回答
    • ためになる回答

    評価が高い回答ほどページの上位に表示されます。

  • 回答の評価を下げる

    下記のような回答は推奨されていません。

    • 間違っている回答
    • 質問の回答になっていない投稿
    • スパムや攻撃的な表現を用いた投稿

    評価を下げる際はその理由を明確に伝え、適切な回答に修正してもらいましょう。

+2

そのcrateにはvoid*をva_listへ変換するC言語のラッパーコードが含まれていて、Rust側ではそのラッパを呼ぶことでprintfを呼んでいます。したがって、そのcrateのprintfのシグネチャはそもそも本来のprintfとは異なるものです。
可変長引数を渡したいなら、...を使うことで可能になります。

extern "C" {
    fn printf(format: *const libc::c_char, ...) -> libc::c_int;
}

fn main(){
    let i: libc::c_int = 42;
    let j: libc::c_double = 3.14;
    let fmt = std::ffi::CString::new("%d %f\n").unwrap();

    unsafe{
        printf(fmt.as_c_str().as_ptr(), i, j);
    }
}

似たような質問をRustのフォーラムで見つけたので貼っておきます。
How to wrap printf in libc properly?

投稿

編集

  • 回答の評価を上げる

    以下のような回答は評価を上げましょう

    • 正しい回答
    • わかりやすい回答
    • ためになる回答

    評価が高い回答ほどページの上位に表示されます。

  • 回答の評価を下げる

    下記のような回答は推奨されていません。

    • 間違っている回答
    • 質問の回答になっていない投稿
    • スパムや攻撃的な表現を用いた投稿

    評価を下げる際はその理由を明確に伝え、適切な回答に修正してもらいましょう。

+1

質問の回答ではないのですが、コードにuse after freeがあるので指摘しておきます。

as_ptrのドキュメントのWarningのコード例と同じことをしてしまっています。
CStringは変数にバインドしていないのでas_ptrを呼び出すところまでしか生存しません。つまりポインタは即座に無効になっています。
以下のようにすれば直ります。

let c_percent_buf = CString::new("%s".to_string()).unwrap();
let c_percent = c_percent_buf.as_ptr() as *const c_char;

let c_string = CString::new(output).unwrap();
let c_str = c_string.as_ptr() as *mut c_void;

printf(c_percent, c_str);

投稿

  • 回答の評価を上げる

    以下のような回答は評価を上げましょう

    • 正しい回答
    • わかりやすい回答
    • ためになる回答

    評価が高い回答ほどページの上位に表示されます。

  • 回答の評価を下げる

    下記のような回答は推奨されていません。

    • 間違っている回答
    • 質問の回答になっていない投稿
    • スパムや攻撃的な表現を用いた投稿

    評価を下げる際はその理由を明確に伝え、適切な回答に修正してもらいましょう。

15分調べてもわからないことは、teratailで質問しよう!

  • ただいまの回答率 88.78%
  • 質問をまとめることで、思考を整理して素早く解決
  • テンプレート機能で、簡単に質問をまとめられる

関連した質問

同じタグがついた質問を見る