For tiny examples like this, I'd read the entire string at once, then split it up on lines. 
use std::fs;
fn main() -> Result<(), Box<dyn std::error::Error>> {
    let contents = fs::read_to_string("input")?;
    for line in contents.trim().lines() {
        let i: i32 = line.trim().parse()?;
        let i = i / 2;
        println!("{}", i);
    }
    Ok(())
}
See also:
For tightly-controlled examples like this, I'd ignore errors occurring while parsing:
use std::fs;
fn main() -> Result<(), Box<dyn std::error::Error>> {
    let contents = fs::read_to_string("input")?;
    for i in contents.trim().lines().flat_map(|l| l.trim().parse::<i32>()) {
        let i = i / 2;
        println!("{}", i);
    }
    Ok(())
}
See also:
For fixed-input examples like this, I'd avoid opening the file at runtime at all, pushing the error to compile-time:
fn main() -> Result<(), Box<dyn std::error::Error>> {
    let contents = include_str!("../input");
    for i in contents.trim().lines().flat_map(|l| l.trim().parse::<i32>()) {
        let i = i / 2;
        println!("{}", i);
    }
    Ok(())
}
See also:
If I wanted to handle failures to parse but treat the iterator as if errors were impossible, I'd use Itertools::process_results:
use itertools; // 0.8.2
fn main() -> Result<(), Box<dyn std::error::Error>> {
    let contents = include_str!("../input");
    let numbers = contents.trim().lines().map(|l| l.trim().parse::<i32>());
    let sum = itertools::process_results(numbers, |i| i.sum::<i32>());
    println!("{:?}", sum);
    Ok(())
}
See also: